Frage

Warum wird die folgende verhalten unerwartet in Python?

>>> a = 256
>>> b = 256
>>> a is b
True           # This is an expected result
>>> a = 257
>>> b = 257
>>> a is b
False          # What happened here? Why is this False?
>>> 257 is 257
True           # Yet the literal numbers compare properly

Ich bin mit Python 2.5.2. einige verschiedene Versionen von Python Der Versuch, es scheint, dass Python 2.3.3 das obige Verhalten zwischen 99 und 100 zeigt.

Basierend auf den oben, kann ich die Hypothese auf, dass Python intern so ausgeführt wird, dass „kleine“ ganze Zahlen in einer anderen Art und Weise gespeichert werden als größere ganze Zahlen und die is Bediener kann den Unterschied erkennen. Warum die undichte Abstraktion? Was ist ein besserer Weg, um zwei beliebige Objekte zu vergleichen, um zu sehen, ob sie die gleichen sind, wenn ich nicht weiß im Voraus, ob sie Zahlen sind oder nicht?

War es hilfreich?

Lösung

Werfen Sie einen Blick auf diese:

>>> a = 256
>>> b = 256
>>> id(a)
9987148
>>> id(b)
9987148
>>> a = 257
>>> b = 257
>>> id(a)
11662816
>>> id(b)
11662828

EDIT: Hier ist, was ich in der Python-2-Dokumentation, "Plain Integer Objekte „ (Es ist das gleiche für Python 3 ):

  

Die aktuelle Implementierung hält ein   Array von Integer-Objekte für alle   ganze Zahlen zwischen -5 und 256, wenn Sie   ein int in diesem Bereich erstellen Sie   erhalten tatsächlich nur einen Verweis auf zurück   das vorhandene Objekt. So sollte es sein   möglich, den Wert von 1 I zu ändern   vermutet, dass das Verhalten von Python in   dieser Fall ist nicht definiert. : -)

Andere Tipps

  

Python „ist“ Operator unerwartet mit ganzen Zahlen verhält?

.

Zusammenfassend - lassen Sie mich betonen: Verwenden is keine ganzen Zahlen vergleichen

Dieses Verhalten nicht ist, sollten Sie keine Erwartungen haben.

Verwenden Sie stattdessen == und != für Gleichheit und Ungleichheit, die jeweils zu vergleichen. Zum Beispiel:

>>> a = 1000
>>> a == 1000       # Test integers like this,
True
>>> a != 5000       # or this!
True
>>> a is 1000       # Don't do this! - Don't use `is` to test integers!!
False

Erklärung

Dies wissen, müssen Sie Folgendes wissen.

Erstens, was tut is zu tun? Es ist ein Vergleichsoperator. Von der Dokumentation :

  

Die Betreiber is und is not Test für Objektidentität: x is y ist wahr   wenn und nur wenn x und y das gleiche Objekt. x is not y ergibt das   inverser Wahrheitswert.

Und so sind äquivalent.

>>> a is b
>>> id(a) == id(b)

Von der Dokumentation :

  

id   Gibt die „Identität“ ein Objekt. Dies ist eine ganze Zahl (oder lange   integer), die für dieses einzigartige und konstant sein Objekt gewährleistet ist   während seiner Lebensdauer. Zwei Objekte mit nicht-überlappenden Lebensdauern können   haben den gleichen id() Wert.

Hinweis

, dass die Tatsache, dass die ID ein Objekt in CPython (die Referenzimplementierung des Pythons) ist die Stelle im Speicher ist ein Implementierungsdetail. Andere Implementierungen von Python (wie Jython oder Ironpython) könnten leicht eine andere Implementierung für id haben.

Was ist also die Use-Case für is? PEP8 beschreibt :

  

Vergleiche zu Singletons wie None sollten immer mit is erfolgen oder   is not, nie die Gleichheitsoperator.

Die Frage

Sie fragen, und Staat, die folgende Frage (mit Code):

  

Warum wird die folgende verhalten unerwartet in Python?

>>> a = 256
>>> b = 256
>>> a is b
True           # This is an expected result

Es ist nicht ein erwartetes Ergebnis. Warum ist es zu erwarten? Es bedeutet nur, dass die ganzen Zahlen bei 256 sowohl a und b verwiesen bewertet die gleiche Instanz von integer sind. Die ganzen Zahlen sind unveränderlich in Python, so können sie nicht ändern. Dies sollte keine Auswirkungen auf jeden Code. Es soll nicht zu erwarten. Es ist lediglich eine Implementierung Detail.

Aber vielleicht sollten wir froh sein, dass es nicht um eine neue separate Instanz im Speicher jedes Mal, wenn wir ein Wert angeben, gleich 256.

>>> a = 257
>>> b = 257
>>> a is b
False          # What happened here? Why is this False?

Sieht aus wie wir jetzt mit dem Wert von 257 im Speicher zwei getrennte Instanzen von ganzen Zahlen haben. Da ganzen Zahlen unveränderlich sind, vergeudet dieser Speicher. Lassen Sie uns hoffen, dass wir nicht viel davon zu verschwenden. Wir sind wahrscheinlich nicht. Aber dieses Verhalten ist nicht garantiert.

>>> 257 is 257
True           # Yet the literal numbers compare properly

Nun, das sieht aus wie Ihre spezielle Implementierung von Python versucht, schlau zu sein und nicht redundant bewertet ganze Zahlen im Speicher zu schaffen, es sei denn es hat. Sie scheinen zeigen Ihnen die referent Implementierung von Python verwenden, die CPython ist. Geeignet für CPython.

Es könnte noch besser sein, wenn CPython dies global tun könnte, wenn es so billig tun könnte (wie es würde eine Kosten in der Look-up), vielleicht eine andere Implementierung könnte.

Aber was Auswirkungen auf Code, sollten Sie es egal, ob eine ganze Zahl eine bestimmte Instanz einer ganzen Zahl ist. Sie sollten nur darum, was der Wert dieser Instanz ist, und Sie würden die normalen Vergleichsoperatoren für die Verwendung, das heißt ==.

Was is tut

is überprüft, ob die id zweier Objekte gleich sind. In CPython ist die id die Stelle im Speicher, aber es könnte eine andere eindeutig identifizierende Nummer in einer anderen Implementierung sein. Neu zu formulieren dies mit Code:

>>> a is b

ist die gleiche wie

>>> id(a) == id(b)

Warum sollten wir wollen dann is benutzen?

Dies kann eine sehr schnelle Überprüfung relativ zu sagen sein, zu überprüfen, ob zwei sehr lange Zeichenkette im Wert gleich sind. Aber da es um die Einzigartigkeit des Objekts gilt, haben wir somit begrenzt Anwendungsfälle für sie. In der Tat wollen wir vor allem, um es für None zu überprüfen, die ein Singleton ist (eine einzige Instanz in einem Ort im Speicher vorhanden). Wir könnten andere Singletons erstellen, wenn es Potenzial ist, sie zu verschmelzen, die wir mit is überprüfen könnten, aber diese sind relativ selten. Hier ist ein Beispiel (wird in Python arbeiten 2 und 3) z.

SENTINEL_SINGLETON = object() # this will only be created one time.

def foo(keyword_argument=None):
    if keyword_argument is None:
        print('no argument given to foo')
    bar()
    bar(keyword_argument)
    bar('baz')

def bar(keyword_argument=SENTINEL_SINGLETON):
    # SENTINEL_SINGLETON tells us if we were not passed anything
    # as None is a legitimate potential argument we could get.
    if keyword_argument is SENTINEL_SINGLETON:
        print('no argument given to bar')
    else:
        print('argument to bar: {0}'.format(keyword_argument))

foo()

Welche druckt:

no argument given to foo
no argument given to bar
argument to bar: None
argument to bar: baz

Und so sehen wir, mit is und einem Wächter, sind wir in der Lage zu unterscheiden zwischen wenn bar ohne Argumente aufgerufen wird, und wenn es mit None genannt wird. Diese sind die wichtigsten Anwendungsfälle für is -. Do nicht verwenden es für die Gleichstellung von ganzen Zahlen zu testen, Strings, Tupel oder andere Dinge wie diese

Es hängt davon ab, ob Sie schauen, um zu sehen, wenn zwei Dinge gleich sind, oder das gleiche Objekt.

is überprüft, ob sie das gleiche Objekt, nicht nur gleich sind. Die kleinen Ints weisen wahrscheinlich auf den gleichen Speicherplatz für Raumeffizienz

In [29]: a = 3
In [30]: b = 3
In [31]: id(a)
Out[31]: 500729144
In [32]: id(b)
Out[32]: 500729144

Sie sollten verwenden == Gleichheit von beliebigen Objekten zu vergleichen. Sie können das Verhalten der __eq__ angeben und __ne__ Attribute.

Ich bin spät, aber wollen Sie einige Quelle mit Ihrer Antwort? *

Gute Sache über CPython ist, dass Sie tatsächlich die Quelle für diese sehen können. Ich werde jetzt Links für die 3.5 Freisetzung verwenden; die entsprechenden 2.x diejenigen zu finden, ist trivial.

In CPython, die C-API-Funktion, die ein neuer int Objekt-Handles zu schaffen ist Objects/ Unterverzeichnis der Haupt Quellcode-Verzeichnisbaum .

PyLong_FromLong beschäftigt sich mit long Objekte so ist es nicht schwer sein sollte, folgern, dass wir hineinschauen müssen longobject.c . Nach innen Sie suchen vielleicht denken, die Dinge sind chaotisch; sie sind, aber keine Angst, die Funktion, die wir suchen ist Chillen unter Lassen Sie uns es Check-out:

#define CHECK_SMALL_INT(ival) \
    do if (-NSMALLNEGINTS <= ival && ival < NSMALLPOSINTS) { \
        return get_small_int((sdigit)ival); \
    } while(0)

So ist es ein Makro ist, die Funktion get_small_int ruft, wenn der Wert ival die Bedingung erfüllt:

if (-NSMALLNEGINTS <= ival && ival < NSMALLPOSINTS)

Also, was ist NSMALLNEGINTS und NSMALLPOSINTS? Wenn Sie Makros Sie ahnen nichts bekommen, weil das nicht so eine schwierige Frage war .. Wie auch immer, hier sind sie :

#ifndef NSMALLPOSINTS
#define NSMALLPOSINTS           257
#endif
#ifndef NSMALLNEGINTS
#define NSMALLNEGINTS           5
#endif

So ist unsere Bedingung if (-5 <= ival && ival < 257) Anruf get_small_int ist.

An keinem anderen Ort zu gehen, aber unsere Reise fortsetzen, indem Sie auf get_small_int in all seine Pracht (na ja, wir werden es den Körper aussehen, nur weil das waren ist die interessanten Dinge):

PyObject *v;
assert(-NSMALLNEGINTS <= ival && ival < NSMALLPOSINTS);
v = (PyObject *)&small_ints[ival + NSMALLNEGINTS];
Py_INCREF(v);

Okay, erklären eine PyObject, behaupten, dass die vorherige Bedingung erfüllt ist, und führen Sie die Zuordnung:

v = (PyObject *)&small_ints[ival + NSMALLNEGINTS];

small_ints sieht viel wie die Array wir .. Suche haben und es ist! konnte Wir haben nur die verdammte Dokumentation lesen und wir würden habe die ganze Zeit wissen :

/* Small integers are preallocated in this array so that they
   can be shared.
   The integers that are preallocated are those in the range
   -NSMALLNEGINTS (inclusive) to NSMALLPOSINTS (not inclusive).
*/
static PyLongObject small_ints[NSMALLNEGINTS + NSMALLPOSINTS];

So yup, das ist unser Mann. Wenn Sie einen neuen int im Bereich erstellen möchten [NSMALLNEGINTS, NSMALLPOSINTS) Sie erhalten nur einen Verweis auf ein bereits vorhandenes Objekt zurück, das vorbelegt wurde.

Da die Referenz bezieht sich auf die gleiche object, Ausgabe id() direkt oder Identität mit is Überprüfung auf sie genau das Gleiche zurück.

Aber, wenn sie zugeordnet ??

Während der Initialisierung in _PyLong_Init Python gerne eingeben in einer for-Schleife dies tun für Sie:

for (ival = -NSMALLNEGINTS; ival <  NSMALLPOSINTS; ival++, v++) {
    // Look me up!
}

Ich hoffe, meine Erklärung hat Sie C (pun offensichtlich intented) die Dinge klar jetzt.


Aber, 257 257? Was ist los?

Dies ist eigentlich leichter zu erklären, und ich habe so schon zu tun versucht ; es ist aufgrund der Tatsache, dass Python wird diese interaktive Anweisung ausführen:

>>> 257 is 257

als ein einzelner Block. Während Compilation dieser Aussage wird CPython sehen, dass Sie zwei passende Literale haben und die gleiche PyLongObject darstellt 257 verwenden. Sie können dies sehen, wenn Sie die Zusammenstellung selbst tun und seinen Inhalt untersuchen:

>>> codeObj = compile("257 is 257", "blah!", "exec")
>>> codeObj.co_consts
(257, None)

Wenn CPython funktioniert die Operation; es ist jetzt gerade dabei genau die gleiche Objekt laden:

>>> import dis
>>> dis.dis(codeObj)
  1           0 LOAD_CONST               0 (257)   # dis
              3 LOAD_CONST               0 (257)   # dis again
              6 COMPARE_OP               8 (is)

So is wird True zurück.


* -. Ich werde versuchen, Wort dies in einer einleitenden Art und Weise, um für die meisten der Lage sein, folgen

Wie Sie überprüfen können, in Quelldatei intobject.c , speichert Python kleine ganze Zahlen für Effizienz. Jedes Mal, wenn Sie einen Verweis auf eine kleine ganze Zahl zu erstellen, die Sie sich beziehen, die im Cache gespeichert kleine ganze Zahl, nicht ein neues Objekt. 257 ist nicht eine kleine ganze Zahl, so wird es als ein anderes Objekt berechnet.

Es ist besser, == zu diesem Zweck zu verwenden.

Ich denke, Ihre Hypothesen richtig ist. Experimentieren Sie mit id (Identität des Objekts):

In [1]: id(255)
Out[1]: 146349024

In [2]: id(255)
Out[2]: 146349024

In [3]: id(257)
Out[3]: 146802752

In [4]: id(257)
Out[4]: 148993740

In [5]: a=255

In [6]: b=255

In [7]: c=257

In [8]: d=257

In [9]: id(a), id(b), id(c), id(d)
Out[9]: (146349024, 146349024, 146783024, 146804020)

Es scheint, dass Zahlen <= 255 als Literale behandelt werden, und alles, was oben ist anders behandelt!

Für unveränderlichen Wert Objekte, wie ints, Strings oder Datetimes, Objektidentität ist nicht besonders nützlich. Es ist besser, um Gleichheit zu denken. Identität ist im Wesentlichen ein Implementierungsdetail für Wertobjekte -., Da sie unveränderlich sind, gibt es keinen effektiven Unterschied zwischen mehreren Refs auf das gleiche Objekt oder mehrere Objekte mit

is ist die Identität Gleichheitsoperator (funktioniert wie id(a) == id(b)); es ist nur, dass zwei gleiche Zahlen sind nicht unbedingt das gleiche Objekt. Aus Performance-Gründen passieren einige kleine ganze Zahlen memoized sein, so dass sie das gleiche sein neigen (dies kann durchgeführt werden, da sie unveränderlich sind).

PHP === Operator, auf der anderen Seite, wird die Überprüfung der Gleichheit und die Art beschrieben: x == y and type(x) == type(y) wie pro Paulo Freitas' Kommentar. Dies wird für die gemeinsame Zahlen genügen, unterscheiden sich aber von is für Klassen, die __eq__ auf absurde Weise zu definieren:

class Unequal:
    def __eq__(self, other):
        return False

PHP ermöglicht es anscheinend das gleiche für „built-in“ Klassen (die ich nehme an C Ebene umgesetzt bedeuten, nicht in PHP). Eine etwas weniger absurd Verwendung könnte ein Timer-Objekt sein, das einen anderen Wert hat ist es jedes Mal als Zahl verwendet. Warum genau würden Sie wollen Visual Basic Now emulieren anstatt zu zeigen, dass es eine Auswertung mit time.time() weiß ich nicht.

Greg Hewgill (OP) machte eine Klärung Kommentars „Mein Ziel ist es Objektidentität zu vergleichen, anstatt Gleichheit Wert. Mit Ausnahme von Zahlen, wo ich Objektidentität der gleiche wie Gleichheit des Wertes behandeln will.“

Dies hätte noch eine andere Antwort, wie wir die Dinge wie Zahlen kategorisieren müssen oder nicht, zu wählen, ob wir mit == oder is vergleichen. CPython definiert die Zahl Protokoll , einschließlich PyNumber_Check, aber dies ist nicht zugänglich von Python selbst.

Wir könnten versuchen isinstance zu verwenden mit allen Zahlentypen wir kennen, aber dies würde zwangsläufig unvollständig sein. Die Typen-Modul enthält eine Liste StringTypes aber keine NumberTypes. Da Python 2.6, haben die in der Anzahl Klassen gebaut eine Basisklasse numbers.Number , aber es hat das gleiche Problem:

import numpy, numbers
assert not issubclass(numpy.int16,numbers.Number)
assert issubclass(int,numbers.Number)

By the way, NumPy separate Instanzen von geringen Stückzahlen produzieren.

Ich weiß nicht wirklich eine Antwort auf diese Variante der Frage. Ich nehme man theoretisch ctypes verwenden könnte PyNumber_Check zu nennen, aber auch die Funktion wurde diskutiert , und es ist sicherlich nicht tragbar. Wir werden einfach sein müssen, weniger insbesondere über das, was wir testen für jetzt.

Am Ende ergibt sich dieses Problem von Python nicht ursprünglich eine Art Baum mit Prädikaten wie mit Scheme number? oder Haskells Typklasse Num . is prüft Objektidentität, nicht Wertgleichheit. PHP hat eine bewegte Geschichte als auch, wo === offenbar als is verhält sich nur auf Objekte in PHP5, aber nicht PHP4 . Solche sind die Wachstumsschmerzen bewegter über Sprachen (einschließlich Versionen eines).

Es ist ein anderes Thema, das in einem der vorhandenen Antworten nicht darauf hingewiesen wird. Python ist erlaubt zwei beliebigen unveränderlichen Werte zu fusionieren, und bereits erstellten kleine int-Werte sind nicht die einzige Art und Weise dies geschehen kann. Eine Python-Implementierung ist nie garantiert , dies zu tun, aber sie alle tun es für mehr als nur kleine Ints.


Für eine Sache, es gibt einige andere bereits erstellten Werte, wie die leere tuple, str und bytes und einige kurze Strings (in CPython 3.6, es ist die 256 Einzelzeichen Latin-1-Strings). Zum Beispiel:

>>> a = ()
>>> b = ()
>>> a is b
True

Aber auch, auch nicht vorab erstellten Werte können identisch sein. Betrachten Sie diese Beispiele:

>>> c = 257
>>> d = 257
>>> c is d
False
>>> e, f = 258, 258
>>> e is f
True

Und das ist nicht beschränkt Wert int:

>>> g, h = 42.23e100, 42.23e100
>>> g is h
True

Offensichtlich ist, dass CPython nicht kommt mit einem bereits erstellten float Wert für 42.23e100. Also, was ist denn hier los?

Das wird CPython Compiler konstante Werte von einigen bekannten-unveränderlichen Typen wie int, float, str, bytes, in derselben Übersetzungseinheit verschmelzen. Für ein Modul ist das gesamte Modul eine Übersetzungseinheit, sondern auf dem interaktiven Interpreter, jede Aussage ist eine separate Übersetzungseinheit. Da c und d in separaten Anweisungen definiert werden, werden ihre Werte nicht verschmolzen. Da e und f in derselben Anweisung definiert sind, werden ihre Werte zusammengefasst.


Sie können sehen, was durch Zerlegen der Bytecode vor sich geht. Versuchen Sie, die eine Funktion, die e, f = 128, 128 tut und dann dis.dis ruft auf, und Sie werden sehen, dass es einen einzigen konstanten Wert (128, 128) ist

>>> def f(): i, j = 258, 258
>>> dis.dis(f)
  1           0 LOAD_CONST               2 ((128, 128))
              2 UNPACK_SEQUENCE          2
              4 STORE_FAST               0 (i)
              6 STORE_FAST               1 (j)
              8 LOAD_CONST               0 (None)
             10 RETURN_VALUE
>>> f.__code__.co_consts
(None, 128, (128, 128))
>>> id(f.__code__.co_consts[1], f.__code__.co_consts[2][0], f.__code__.co_consts[2][1])
4305296480, 4305296480, 4305296480

Sie werden feststellen, dass der Compiler 128 als Konstante gespeichert hat, obwohl es ist nicht wirklich von der Bytecode verwendet, die Ihnen eine Vorstellung davon, wie wenig Optimierung CPython den Compiler gibt den Fall ist. Was bedeutet, dass (nicht leer) Tupel eigentlich nicht fusioniert am Ende:

>>> k, l = (1, 2), (1, 2)
>>> k is l
False

Setzen Sie, dass in einer Funktion, dis es, und Blick auf die co_consts-es gibt eine 1 und ein 2, zwei (1, 2) Tupel, die die gleiche 1 und 2 teilen, sind aber nicht identisch, und ein ((1, 2), (1, 2)) Tupel, das die zwei verschiedene gleich hat Tupel.


Es gibt eine weitere Optimierung, die CPython tut: string Internierung. Im Gegensatz zu Compiler konstantem Falten, ist dies nicht auf dem Quellcode Literale eingeschränkt:

>>> m = 'abc'
>>> n = 'abc'
>>> m is n
True

Auf der anderen Seite ist es auf den str Typen beschränkt ist und in Strings von interner Speicher Art "ascii kompakt", "kompakt" oder "Legacy-ready" , und in vielen Fällen nur "ascii compact" wird interniert erhalten.


Auf jeden Fall sind die Regeln für welche Werte sein muss, auch sein mag, oder kann nicht unterscheiden sich von Implementierung zu Implementierung variieren sein, und zwischen Versionen derselben Implementierung, und vielleicht sogar zwischen den Läufen des gleichen Codes auf der gleichen Kopie die gleiche Umsetzung.

Es kann für den Spaß von ihm die Regeln für eine bestimmte Python Lernen wert sein. Aber es ist nicht wert, auf sie in Ihrem Code zu verlassen. Die einzige sichere Regel ist:

  • Schreiben Sie nicht Code, der zwei gleich große, aber separat erstellten unveränderliche Werte sind identisch annimmt.
  • Schreiben Sie nicht Code, der zwei gleich große, aber separat erstellten unveränderliche Werte unterscheiden sich annimmt.

Oder, mit anderen Worten, nur verwenden is für die dokumentierten Singletons zu testen (wie None) oder das ist nur an einer Stelle im Code (wie das _sentinel = object() Idiom) erstellt.

Es kommt auch mit Strings:

>>> s = b = 'somestr'
>>> s == b, s is b, id(s), id(b)
(True, True, 4555519392, 4555519392)

Jetzt scheint alles in Ordnung.

>>> s = 'somestr'
>>> b = 'somestr'
>>> s == b, s is b, id(s), id(b)
(True, True, 4555519392, 4555519392)

Das ist erwartet zu.

>>> s1 = b1 = 'somestrdaasd ad ad asd as dasddsg,dlfg ,;dflg, dfg a'
>>> s1 == b1, s1 is b1, id(s1), id(b1)
(True, True, 4555308080, 4555308080)

>>> s1 = 'somestrdaasd ad ad asd as dasddsg,dlfg ,;dflg, dfg a'
>>> b1 = 'somestrdaasd ad ad asd as dasddsg,dlfg ,;dflg, dfg a'
>>> s1 == b1, s1 is b1, id(s1), id(b1)
(True, False, 4555308176, 4555308272)

Nun, das ist unerwartet.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top