Der Operator "is" verhält sich unerwartet mit ganzen Zahlen


509

Warum verhält sich Folgendes in Python unerwartet?

>>> 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 benutze Python 2.5.2. Beim Versuch, verschiedene Versionen von Python zu verwenden, scheint Python 2.3.3 das obige Verhalten zwischen 99 und 100 zu zeigen.

Basierend auf dem oben Gesagten kann ich die Hypothese aufstellen, dass Python intern so implementiert ist, dass "kleine" Ganzzahlen anders gespeichert werden als größere Ganzzahlen und der isOperator den Unterschied erkennen kann. Warum die undichte Abstraktion? Was ist ein besserer Weg, um zwei beliebige Objekte zu vergleichen, um festzustellen, ob sie gleich sind, wenn ich nicht im Voraus weiß, ob es sich um Zahlen handelt oder nicht?


1
Schauen Sie hier nach > Die aktuelle Implementierung behält ein Array von Ganzzahlobjekten für alle> Ganzzahlen zwischen -5 und 256 bei. Wenn Sie ein Int in diesem Bereich erstellen, erhalten Sie> nur einen Verweis auf das vorhandene Objekt zurück.
user5319825

2
Dies ist ein CPython-spezifisches Implementierungsdetail und ein undefiniertes Verhalten, das mit Vorsicht verwendet wird
ospider

Antworten:


393

Schau dir das an:

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

Folgendes habe ich in der Python 2-Dokumentation "Plain Integer Objects" gefunden (dies gilt auch für Python 3 ):

Die aktuelle Implementierung behält ein Array von Ganzzahlobjekten für alle Ganzzahlen zwischen -5 und 256 bei. Wenn Sie ein Int in diesem Bereich erstellen, erhalten Sie tatsächlich nur einen Verweis auf das vorhandene Objekt zurück. Es sollte also möglich sein, den Wert 1 zu ändern. Ich vermute, dass das Verhalten von Python in diesem Fall undefiniert ist. :-)


46
Weiß jemand, wie dieser Bereich (-5, 256) gewählt wurde? Ich wäre nicht allzu überrascht, wenn es (0, 255) oder sogar (-255, 255) wäre, aber ein Bereich von 262 Zahlen ab -5 scheint überraschend willkürlich.
Woodrow Barlow

6
@WoodrowBarlow: Die -5 ist nur eine Heuristik, um häufig verwendete negative Platzhalter zu erfassen, denke ich. 0..255 deckt Arrays mit Einzelbytewerten ab. Es ist 256, was mysteriös ist, aber ich denke, es ist für das (Dis-) Assemblieren von ganzen Zahlen in / aus Bytes.
Davis Herring

3
Soweit ich weiß, wurde der Bereich anhand der häufig verwendeten Werte in mehreren Projekten (und mehreren Sprachen) ausgewählt.
Tony Suffolk 66

9
Laut reddit.com/r/Python/comments/18leav/… lag der Bereich früher bei [-5.100]. Es wurde um den gesamten Bereich der Bytewerte erweitert - plus 256, da dies vermutlich eine gebräuchliche Zahl ist.
mwfearnley

2
@Ashwani Wenn Sie versuchen, die Kommentare direkt neben Ihrem Kommentar zu lesen, der zwei Jahre vor Ihrem veröffentlicht wurde, finden Sie die Antwort auf Ihre Frage.
jbg

116

Pythons "is" -Operator verhält sich unerwartet mit ganzen Zahlen?

Zusammenfassend - lassen Sie mich betonen: Nicht iszum Vergleichen von ganzen Zahlen verwenden.

Dies ist kein Verhalten, über das Sie irgendwelche Erwartungen haben sollten.

Verwenden Sie stattdessen ==und !=, um Gleichheit bzw. Ungleichheit 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

Erläuterung

Um dies zu wissen, müssen Sie Folgendes wissen.

Was macht zuerst is? Es ist ein Vergleichsoperator. Aus der Dokumentation :

Die Operatoren isund der is notTest auf Objektidentität: x is yist genau dann wahr, wenn x und y dasselbe Objekt sind. x is not yergibt den inversen Wahrheitswert.

Und so sind die folgenden gleichwertig.

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

Aus der Dokumentation :

id Gibt die "Identität" eines Objekts zurück. Dies ist eine Ganzzahl (oder lange Ganzzahl), die für dieses Objekt während seiner Lebensdauer garantiert eindeutig und konstant ist. Zwei Objekte mit nicht überlappenden Lebensdauern können denselben id()Wert haben.

Beachten Sie, dass die Tatsache, dass die ID eines Objekts in CPython (die Referenzimplementierung von Python) der Speicherort im Speicher ist, ein Implementierungsdetail ist. Andere Implementierungen von Python (wie Jython oder IronPython) könnten leicht eine andere Implementierung für haben id.

Wofür ist der Anwendungsfall is? PEP8 beschreibt :

Vergleiche mit Singletons wie Nonesollten immer mit isoder is notniemals mit den Gleichheitsoperatoren durchgeführt werden.

Die Frage

Sie stellen die folgende Frage (mit Code) und geben sie an:

Warum verhält sich Folgendes in Python unerwartet?

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

Es ist kein erwartetes Ergebnis. Warum wird es erwartet? Es bedeutet nur , dass die ganzen Zahlen an Wert 256sowohl referenziert aund bsind die gleiche Instanz integer. Ganzzahlen sind in Python unveränderlich und können sich daher nicht ändern. Dies sollte keine Auswirkungen auf Code haben. Es sollte nicht erwartet werden. Es ist lediglich ein Implementierungsdetail.

Aber vielleicht sollten wir froh sein, dass es nicht jedes Mal eine neue separate Instanz im Speicher gibt, wenn wir einen Wert von 256 angeben.

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

Sieht so aus, als hätten wir jetzt zwei separate Instanzen von Ganzzahlen mit dem Wert 257im Speicher. Da Ganzzahlen unveränderlich sind, wird Speicher verschwendet. Hoffen wir, dass wir nicht viel davon verschwenden. Wir sind wahrscheinlich nicht. Dieses Verhalten ist jedoch nicht garantiert.

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

Nun, es sieht so aus, als ob Ihre spezielle Implementierung von Python versucht, intelligent zu sein und keine redundant bewerteten Ganzzahlen im Speicher zu erstellen, es sei denn, dies ist erforderlich. Sie scheinen anzugeben, dass Sie die Referenzimplementierung von Python verwenden, nämlich CPython. Gut für CPython.

Es könnte sogar noch besser sein, wenn CPython dies global tun könnte, wenn es dies kostengünstig tun könnte (da die Suche Kosten verursachen würde), könnte möglicherweise eine andere Implementierung erfolgen.

Was die Auswirkungen auf den Code betrifft, sollte es Ihnen egal sein, ob eine Ganzzahl eine bestimmte Instanz einer Ganzzahl ist. Sie sollten sich nur darum kümmern, wie hoch der Wert dieser Instanz ist, und Sie würden dafür die normalen Vergleichsoperatoren verwenden, d ==. H.

Was ismacht

isprüft, ob die idbeiden Objekte gleich sind. In CPython idist dies der Speicherort im Speicher, es kann sich jedoch auch um eine andere eindeutig identifizierende Nummer in einer anderen Implementierung handeln. So wiederholen Sie dies mit Code:

>>> a is b

ist das gleiche wie

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

Warum sollten wir dann verwenden wollen is?

Dies kann eine sehr schnelle Überprüfung sein, um beispielsweise zu überprüfen, ob zwei sehr lange Zeichenfolgen den gleichen Wert haben. Da es sich jedoch um die Einzigartigkeit des Objekts handelt, haben wir nur begrenzte Anwendungsfälle dafür. Tatsächlich möchten wir es meistens verwenden, um zu überprüfen, ob es sich um Noneeinen Singleton handelt (eine einzige Instanz, die an einer Stelle im Speicher vorhanden ist). Wir könnten andere Singletons erstellen, wenn das Potenzial besteht, sie zusammenzuführen, mit denen wir prüfen könnten is, aber diese sind relativ selten. Hier ist ein Beispiel (funktioniert in Python 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 Drucke:

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

Und so sehen wir, dass wir mit isund einem Sentinel unterscheiden können, wann barohne Argumente aufgerufen wird und wann mit None. Diese sind die wichtigsten Anwendungsfälle für is- Sie nicht es für die Gleichstellung von ganzen Zahlen Test verwenden, Strings, Tupel oder andere Dinge wie diese.


"Dies sind die Hauptanwendungsfälle für is- verwenden Sie sie nicht, um die Gleichheit von Ganzzahlen, Zeichenfolgen, Tupeln oder ähnlichen Dingen zu testen." Ich versuche jedoch, eine einfache Zustandsmaschine in meine Klasse zu integrieren, und da es sich bei den Zuständen um undurchsichtige Werte handelt, deren einzige beobachtbare Eigenschaft darin besteht, identisch oder unterschiedlich zu sein, erscheint es für sie ganz natürlich, mit ihnen vergleichbar zu sein is. Ich plane, internierte Zeichenfolgen als Zustände zu verwenden. Ich hätte einfache Ganzzahlen bevorzugt, aber Python kann leider keine Ganzzahlen internieren ( 0 is 0ist ein Implementierungsdetail).
Alexey

@ Alexander klingt wie Sie brauchen Aufzählungen? stackoverflow.com/questions/37601644/…
Aaron Hall

Vielleicht, danke, wusste nicht von ihnen. Dies könnte eine angemessene Ergänzung zu Ihrer Antwort IMO sein.
Alexey

Vielleicht wäre die Verwendung einer Reihe von dummen Objekten wie dem Sentinel in Ihrer Antwort eine leichtere Lösung ...
Alexey

@ Alexey-Enums befinden sich in der Python 3-Standardbibliothek, und das würde wahrscheinlich dazu führen, dass Ihr Code ein bisschen aussagekräftiger ist als reine Sentinels.
Aaron Hall

60

Es hängt davon ab, ob Sie sehen möchten, ob zwei Dinge gleich sind oder dasselbe Objekt.

isprüft, ob es sich um dasselbe Objekt handelt, nicht nur um dasselbe. Die kleinen Ints zeigen wahrscheinlich auf denselben Speicherort, um Platz zu sparen

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 ==, um die Gleichheit beliebiger Objekte zu vergleichen. Sie können das Verhalten mit den Attributen __eq__und angeben __ne__.


Daumen hoch für die tatsächliche Erklärung, wie man beliebige Objekte vergleicht, wie das OP gefragt hat !!
Joooeey

54

Ich bin spät dran, aber Sie möchten eine Quelle mit Ihrer Antwort? Ich werde versuchen, dies einleitend zu formulieren, damit weitere Leute folgen können.


Eine gute Sache bei CPython ist, dass Sie tatsächlich die Quelle dafür sehen können. Ich werde Links für die Version 3.5 verwenden , aber die entsprechenden 2.x- Links zu finden ist trivial.

In CPython lautet die C-API- Funktion zum Erstellen eines neuen intObjekts PyLong_FromLong(long v). Die Beschreibung für diese Funktion lautet:

Die aktuelle Implementierung behält ein Array von Ganzzahlobjekten für alle Ganzzahlen zwischen -5 und 256 bei. Wenn Sie ein Int in diesem Bereich erstellen, erhalten Sie tatsächlich nur einen Verweis auf das vorhandene Objekt zurück . Es sollte also möglich sein, den Wert 1 zu ändern. Ich vermute, dass das Verhalten von Python in diesem Fall undefiniert ist. :-)

(Meine Kursivschrift)

Ich weiß nichts über dich, aber ich sehe das und denke: Lass uns das Array finden!

Wenn Sie nicht mit dem C-Code herumgespielt haben, der CPython implementiert , sollten Sie dies tun . alles ist ziemlich gut organisiert und lesbar. Für unseren Fall müssen wir im ObjectsUnterverzeichnis des Hauptquellcode-Verzeichnisbaums suchen .

PyLong_FromLongbefasst sich mit longObjekten, daher sollte es nicht schwer sein, daraus zu schließen, dass wir einen Blick hineinwerfen müssen longobject.c. Wenn Sie nach innen schauen, denken Sie vielleicht, dass die Dinge chaotisch sind. Sie sind, aber keine Angst, die Funktion, nach der wir suchen, ist, in Zeile 230 zu chillen und darauf zu warten, dass wir sie überprüfen. Da es sich um eine kleinere Funktion handelt, kann der Hauptteil (ohne Deklarationen) hier leicht eingefügt werden:

PyObject *
PyLong_FromLong(long ival)
{
    // omitting declarations

    CHECK_SMALL_INT(ival);

    if (ival < 0) {
        /* negate: cant write this as abs_ival = -ival since that
           invokes undefined behaviour when ival is LONG_MIN */
        abs_ival = 0U-(unsigned long)ival;
        sign = -1;
    }
    else {
        abs_ival = (unsigned long)ival;
    }

    /* Fast path for single-digit ints */
    if (!(abs_ival >> PyLong_SHIFT)) {
        v = _PyLong_New(1);
        if (v) {
            Py_SIZE(v) = sign;
            v->ob_digit[0] = Py_SAFE_DOWNCAST(
                abs_ival, unsigned long, digit);
        }
        return (PyObject*)v; 
}

Jetzt sind wir kein C- Master-Code-Haxxorz, aber wir sind auch nicht dumm. Wir können sehen, dass CHECK_SMALL_INT(ival);wir alle verführerisch angesehen werden. wir können verstehen, dass es etwas damit zu tun hat. Schauen wir es uns an:

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

Es ist also ein Makro, das die Funktion aufruft, get_small_intwenn der Wert ivaldie Bedingung erfüllt:

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

Also was sind NSMALLNEGINTSund NSMALLPOSINTS? Makros! Hier sind sie :

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

Unser Zustand ist also if (-5 <= ival && ival < 257)Anruf get_small_int.

Als nächstes schauen wir uns get_small_intseine ganze Pracht an (nun, wir schauen uns nur seinen Körper an, denn dort sind die interessanten Dinge):

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

Okay, deklarieren Sie a PyObject, behaupten Sie, dass die vorherige Bedingung gilt, und führen Sie die Zuweisung aus:

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

small_intssieht dem Array, nach dem wir gesucht haben, sehr ähnlich, und das ist es auch! Wir hätten einfach die verdammte Dokumentation lesen können und wir hätten es die ganze Zeit gewusst! ::

/* 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];

Also ja, das ist unser Typ. Wenn Sie ein neues Objekt intim Bereich erstellen möchten, erhalten [NSMALLNEGINTS, NSMALLPOSINTS)Sie nur einen Verweis auf ein bereits vorhandenes Objekt zurück, das vorab zugewiesen wurde.

Da sich die Referenz auf dasselbe Objekt bezieht, wird durch id()direktes Ausgeben oder Überprüfen der Identität mit isgenau dasselbe zurückgegeben.

Aber wann werden sie zugeteilt?

Während der Initialisierung in_PyLong_Init Python wird gerne eine for-Schleife eingegeben. Tun Sie dies für Sie:

for (ival = -NSMALLNEGINTS; ival <  NSMALLPOSINTS; ival++, v++) {

Überprüfen Sie die Quelle, um den Schleifenkörper zu lesen!

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


Aber 257 is 257? Wie geht's?

Dies ist tatsächlich einfacher zu erklären, und ich habe bereits versucht, dies zu tun . Dies liegt an der Tatsache, dass Python diese interaktive Anweisung als einzelnen Block ausführt:

>>> 257 is 257

Während des Abschlusses dieser Anweisung stellt CPython fest, dass Sie zwei übereinstimmende Literale haben und dieselbe PyLongObjectDarstellung verwenden 257. Sie können dies sehen, wenn Sie die Zusammenstellung selbst durchführen und deren Inhalt untersuchen:

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

Wenn CPython die Operation ausführt, wird jetzt genau dasselbe Objekt geladen:

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

Also iswerde ich zurückkehren True.


37

Wie Sie in der Quelldatei intobject.c einchecken können Python aus Effizienz kleine Ganzzahlen zwischen. Jedes Mal, wenn Sie einen Verweis auf eine kleine Ganzzahl erstellen, verweisen Sie auf die zwischengespeicherte kleine Ganzzahl, nicht auf ein neues Objekt. 257 ist keine kleine Ganzzahl, daher wird sie als anderes Objekt berechnet.

Es ist besser, ==für diesen Zweck zu verwenden.


19

Ich denke, Ihre Hypothesen sind richtig. Experimentieren 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 <= 255als Literale behandelt werden und alles oben Genannte anders behandelt wird!


1
Dies liegt daran, dass Objekte, die Werte von -5 bis +256 darstellen, beim Start erstellt werden. Daher wird jede Verwendung dieser Werte für vorgefertigte Objekte verwendet. Fast alle Verweise auf Ganzzahlen außerhalb dieses Bereichs erstellen bei jeder Referenzierung ein neues internes Objekt. Ich denke, die Verwendung des Begriffs Literal ist verwirrend - Literal bezieht sich normalerweise auf jeden Wert, der in einen Code eingegeben wird - daher sind alle Zahlen im Quellcode Literale.
Tony Suffolk 66

13

Für unveränderliche Wertobjekte wie Ints, Strings oder Datumsangaben ist die Objektidentität nicht besonders nützlich. Es ist besser, über Gleichheit nachzudenken. Identität ist im Wesentlichen ein Implementierungsdetail für Wertobjekte. Da sie unveränderlich sind, gibt es keinen effektiven Unterschied zwischen mehreren Verweisen auf dasselbe Objekt oder mehrere Objekte.


12

Es gibt noch ein anderes Problem, auf das in keiner der vorhandenen Antworten hingewiesen wird. Python darf zwei unveränderliche Werte zusammenführen, und vorab erstellte kleine int-Werte sind nicht die einzige Möglichkeit, wie dies geschehen kann. Eine Python-Implementierung garantiert dies niemals , aber alle tun dies für mehr als nur kleine Ints.


Für eine Sache, es gibt einige andere bereits erstellten Werte, wie zum Beispiel die leer sind tuple, strund 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 nicht vorab erstellte 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 auf intWerte beschränkt :

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

Offensichtlich enthält CPython keinen vorab erstellten floatWert für 42.23e100. Also, was ist hier los?

Der CPython Compiler konstante Werte von einigen bekannten Typen-unveränderlichen verschmelzen wie int, float, str, bytes, in derselben Übersetzungseinheit. Für ein Modul ist das gesamte Modul eine Kompilierungseinheit, aber beim interaktiven Interpreter ist jede Anweisung eine separate Kompilierungseinheit. Da cund din separaten Anweisungen definiert sind, werden ihre Werte nicht zusammengeführt. Da eund fin derselben Anweisung definiert sind, werden ihre Werte zusammengeführt.


Sie können sehen, was los ist, indem Sie den Bytecode zerlegen. Wenn Sie eine Funktion definieren, die dies tut, e, f = 128, 128und sie dann aufrufen dis.dis, werden Sie feststellen, dass es einen einzelnen konstanten Wert gibt(128, 128)

>>> 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

Möglicherweise stellen Sie fest, dass der Compiler 128als Konstante gespeichert wurde , obwohl er vom Bytecode nicht tatsächlich verwendet wird. Dadurch erhalten Sie eine Vorstellung davon, wie wenig der CPython-Compiler optimiert. Was bedeutet, dass (nicht leere) Tupel tatsächlich nicht zusammengeführt werden:

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

Setzen Sie, dass in einer Funktion, dissie und Blick auf die co_consts-es ist ein 1und ein 2, zwei (1, 2)Tupel, die den gleichen teilen 1und 2aber nicht identisch sind , und ein ((1, 2), (1, 2))Tupel, das die zwei verschiedene gleich Tupeln hat.


CPython führt noch eine weitere Optimierung durch: String-Internierung. Im Gegensatz zum konstanten Falten des Compilers ist dies nicht auf Quellcodeliterale beschränkt:

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

Andererseits ist es auf den strTyp und auf Zeichenfolgen der internen Speicherart "ascii compact", "compact" oder "Legacy Ready" beschränkt , und in vielen Fällen wird nur "ascii compact" interniert.


In jedem Fall variieren die Regeln dafür, welche Werte unterschiedlich sein müssen, sein können oder nicht, von Implementierung zu Implementierung und zwischen Versionen derselben Implementierung und möglicherweise sogar zwischen Läufen desselben Codes auf derselben Kopie derselben Implementierung .

Es kann sich lohnen, zum Spaß die Regeln für eine bestimmte Python zu lernen. Es lohnt sich jedoch nicht, sich in Ihrem Code auf sie zu verlassen. Die einzig sichere Regel ist:

  • Schreiben Sie keinen Code, der davon ausgeht, dass zwei gleiche, aber separat erstellte unveränderliche Werte identisch sind (nicht verwenden x is y, verwenden x == y).
  • Schreiben Sie keinen Code, der davon ausgeht, dass zwei gleiche, aber separat erstellte unveränderliche Werte unterschiedlich sind (nicht verwenden x is not y, verwenden x != y).

Oder mit anderen Worten, nur iszum Testen auf dokumentierte Singletons (wie None) verwenden, die nur an einer Stelle im Code erstellt werden (wie die _sentinel = object()Redewendung).


Der weniger kryptische Rat ist einfach: Nicht x is yzum Vergleichen verwenden, verwenden x == y. Ebenso nicht benutzen x is not y, benutzenx != y
smci

Wenn Sie sich also diese Frage ansehen , warum ist a=257; b=257in einer Zeile a is bTrue
Joe

8

is ist der Identitätsgleichheitsoperator (funktioniert wie id(a) == id(b)); Es ist nur so, dass zwei gleiche Zahlen nicht unbedingt dasselbe Objekt sind. Aus Leistungsgründen werden einige kleine Ganzzahlen gespeichert, sodass sie in der Regel gleich sind (dies kann erfolgen, da sie unveränderlich sind).

Der PHP- === Operator hingegen wird als Überprüfung von Gleichheit und Typ beschrieben: x == y and type(x) == type(y)gemäß dem Kommentar von Paulo Freitas. Dies wird für gebräuchliche Zahlen ausreichen, unterscheidet sich jedoch von isKlassen, die __eq__auf absurde Weise definieren:

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

PHP erlaubt anscheinend dasselbe für "eingebaute" Klassen (was ich als auf C-Ebene implementiert meine, nicht in PHP). Eine etwas weniger absurde Verwendung könnte ein Timer-Objekt sein, das jedes Mal, wenn es als Zahl verwendet wird, einen anderen Wert hat. Warum Sie Visual Basic emulieren möchten, Nowanstatt zu zeigen, dass es sich um eine Evaluierung handelt, time.time()weiß ich nicht.

Greg Hewgill (OP) machte einen klarstellenden Kommentar: "Mein Ziel ist es, die Objektidentität und nicht die Wertgleichheit zu vergleichen. Mit Ausnahme von Zahlen, bei denen ich die Objektidentität genauso behandeln möchte wie die Wertgleichheit."

Dies hätte noch eine andere Antwort, da wir die Dinge als Zahlen kategorisieren müssen oder nicht, um auszuwählen, ob wir mit vergleichen == oderis . CPython definiert das Nummernprotokoll , einschließlich PyNumber_Check, aber auf Python selbst kann nicht zugegriffen werden.

Wir könnten versuchen zu verwenden isinstance mit allen uns bekannten Zahlentypen zu arbeiten, aber dies wäre unweigerlich unvollständig. Das Typmodul enthält eine StringTypes-Liste, jedoch keine NumberTypes. Seit Python 2.6 haben die eingebauten Zahlenklassen eine Basisklasse numbers.Number, aber das gleiche Problem:

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

Übrigens wird NumPy separate Instanzen mit niedrigen Zahlen erzeugen.

Ich kenne eigentlich keine Antwort auf diese Variante der Frage. Ich nehme an, man könnte theoretisch ctypes zum Aufrufen verwendenPyNumber_Check , aber selbst diese Funktion wurde diskutiert , und sie ist sicherlich nicht portabel. Wir müssen nur weniger genau wissen, was wir jetzt testen.

Letztendlich ist dieses Problem darauf zurückzuführen, dass Python ursprünglich keinen Typbaum mit Prädikaten wie Scheme number? oder Haskells Typklasse Num hat .isÜberprüft die Objektidentität, nicht die Wertgleichheit. PHP hat auch eine bunte Geschichte, in der es sich ===anscheinend isnur auf Objekten verhält in PHP5 , nicht jedoch PHP4 . Dies sind die wachsenden Schmerzen beim Übergang zwischen Sprachen (einschließlich Versionen von einer).


4

Es passiert auch mit Strings:

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

Jetzt scheint alles in Ordnung zu sein.

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

Das wird auch erwartet.

>>> 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)

Das ist unerwartet.


Passiert darauf - stimmte zu, dass noch seltsamer. Also habe ich damit gespielt und es ist noch seltsamer - bezogen auf den Raum. Zum Beispiel ist die Zeichenfolge 'xx'wie erwartet 'xxx', aber 'x x'nicht.
Brian

2
Das liegt daran, dass es wie ein Symbol aussieht, wenn kein Leerzeichen darin ist. Namen werden automatisch interniert. Wenn also xxirgendwo in Ihrer Python-Sitzung etwas benannt ist, ist diese Zeichenfolge bereits interniert. und es könnte eine Heuristik geben, die dies tut, wenn sie nur einem Namen ähnelt. Wie bei Zahlen kann dies geschehen, weil sie unveränderlich sind. docs.python.org/2/library/functions.html#intern guilload.com/python-string-interning
Yann Vernier

3

Was ist neu in Python 3.8: Änderungen im Python-Verhalten :

Der Compiler erzeugt jetzt eine SyntaxWarning, wenn Identitätsprüfungen ( isund is not) mit bestimmten Arten von Literalen (z. B. Zeichenfolgen, Ints) verwendet werden. Diese können in CPython häufig versehentlich funktionieren, werden jedoch durch die Sprachspezifikation nicht garantiert. In der Warnung wird den Benutzern empfohlen, stattdessen Gleichheitstests ( == und !=) zu verwenden.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.