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 is
Operator 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?
Antworten:
Schau dir das an:
Folgendes habe ich in der Python 2-Dokumentation "Plain Integer Objects" gefunden (dies gilt auch für Python 3 ):
quelle
Zusammenfassend - lassen Sie mich betonen: Nicht
is
zum 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:Erläuterung
Um dies zu wissen, müssen Sie Folgendes wissen.
Was macht zuerst
is
? Es ist ein Vergleichsoperator. Aus der Dokumentation :Und so sind die folgenden gleichwertig.
Aus der Dokumentation :
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 :Die Frage
Sie stellen die folgende Frage (mit Code) und geben sie an:
Es ist kein erwartetes Ergebnis. Warum wird es erwartet? Es bedeutet nur , dass die ganzen Zahlen an Wert
256
sowohl referenzierta
undb
sind 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.
Sieht so aus, als hätten wir jetzt zwei separate Instanzen von Ganzzahlen mit dem Wert
257
im 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.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
is
machtis
prüft, ob dieid
beiden Objekte gleich sind. In CPythonid
ist 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:ist das gleiche wie
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
None
einen 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önntenis
, aber diese sind relativ selten. Hier ist ein Beispiel (funktioniert in Python 2 und 3), zWelche Drucke:
Und so sehen wir, dass wir mit
is
und einem Sentinel unterscheiden können, wannbar
ohne Argumente aufgerufen wird und wann mitNone
. Diese sind die wichtigsten Anwendungsfälle füris
- Sie nicht es für die Gleichstellung von ganzen Zahlen Test verwenden, Strings, Tupel oder andere Dinge wie diese.quelle
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 seinis
. Ich plane, internierte Zeichenfolgen als Zustände zu verwenden. Ich hätte einfache Ganzzahlen bevorzugt, aber Python kann leider keine Ganzzahlen internieren (0 is 0
ist ein Implementierungsdetail).Es hängt davon ab, ob Sie sehen möchten, ob zwei Dinge gleich sind oder dasselbe Objekt.
is
prüft, ob es sich um dasselbe Objekt handelt, nicht nur um dasselbe. Die kleinen Ints zeigen wahrscheinlich auf denselben Speicherort, um Platz zu sparenSie sollten verwenden
==
, um die Gleichheit beliebiger Objekte zu vergleichen. Sie können das Verhalten mit den Attributen__eq__
und angeben__ne__
.quelle
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
int
ObjektsPyLong_FromLong(long v)
. Die Beschreibung für diese Funktion lautet:(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
Objects
Unterverzeichnis des Hauptquellcode-Verzeichnisbaums suchen .PyLong_FromLong
befasst sich mitlong
Objekten, daher sollte es nicht schwer sein, daraus zu schließen, dass wir einen Blick hineinwerfen müssenlongobject.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: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:Es ist also ein Makro, das die Funktion aufruft,
get_small_int
wenn der Wertival
die Bedingung erfüllt:Also was sind
NSMALLNEGINTS
undNSMALLPOSINTS
? Makros! Hier sind sie :Unser Zustand ist also
if (-5 <= ival && ival < 257)
Anrufget_small_int
.Als nächstes schauen wir uns
get_small_int
seine ganze Pracht an (nun, wir schauen uns nur seinen Körper an, denn dort sind die interessanten Dinge):Okay, deklarieren Sie a
PyObject
, behaupten Sie, dass die vorherige Bedingung gilt, und führen Sie die Zuweisung aus:small_ints
sieht 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! ::Also ja, das ist unser Typ. Wenn Sie ein neues Objekt
int
im 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 mitis
genau 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:Ü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:
Während des Abschlusses dieser Anweisung stellt CPython fest, dass Sie zwei übereinstimmende Literale haben und dieselbe
PyLongObject
Darstellung verwenden257
. Sie können dies sehen, wenn Sie die Zusammenstellung selbst durchführen und deren Inhalt untersuchen:Wenn CPython die Operation ausführt, wird jetzt genau dasselbe Objekt geladen:
Also
is
werde ich zurückkehrenTrue
.quelle
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.quelle
Ich denke, Ihre Hypothesen sind richtig. Experimentieren mit
id
(Identität des Objekts):Es scheint, dass Zahlen
<= 255
als Literale behandelt werden und alles oben Genannte anders behandelt wird!quelle
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.
quelle
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
,str
undbytes
, und einige kurze Strings (in CPython 3.6, es ist die 256 Einzelzeichen Latin-1 - Strings). Zum Beispiel:Aber auch nicht vorab erstellte Werte können identisch sein. Betrachten Sie diese Beispiele:
Und das ist nicht auf
int
Werte beschränkt :Offensichtlich enthält CPython keinen vorab erstellten
float
Wert für42.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. Dac
undd
in separaten Anweisungen definiert sind, werden ihre Werte nicht zusammengeführt. Dae
undf
in 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, 128
und sie dann aufrufendis.dis
, werden Sie feststellen, dass es einen einzelnen konstanten Wert gibt(128, 128)
Möglicherweise stellen Sie fest, dass der Compiler
128
als 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:Setzen Sie, dass in einer Funktion,
dis
sie und Blick auf dieco_consts
-es ist ein1
und ein2
, zwei(1, 2)
Tupel, die den gleichen teilen1
und2
aber 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:
Andererseits ist es auf den
str
Typ 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:
x is y
, verwendenx == y
).x is not y
, verwendenx != y
).Oder mit anderen Worten, nur
is
zum Testen auf dokumentierte Singletons (wieNone
) verwenden, die nur an einer Stelle im Code erstellt werden (wie die_sentinel = object()
Redewendung).quelle
x is y
zum Vergleichen verwenden, verwendenx == y
. Ebenso nicht benutzenx is not y
, benutzenx != y
a=257; b=257
in einer Zeilea is b
Trueis
ist der Identitätsgleichheitsoperator (funktioniert wieid(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 vonis
Klassen, die__eq__
auf absurde Weise definieren: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,
Now
anstatt 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 Basisklassenumbers.Number
, aber das gleiche Problem:Ü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 verwenden
PyNumber_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===
anscheinendis
nur auf Objekten verhält in PHP5 , nicht jedoch PHP4 . Dies sind die wachsenden Schmerzen beim Übergang zwischen Sprachen (einschließlich Versionen von einer).quelle
Es passiert auch mit Strings:
Jetzt scheint alles in Ordnung zu sein.
Das wird auch erwartet.
Das ist unerwartet.
quelle
'xx'
wie erwartet'xxx'
, aber'x x'
nicht.xx
irgendwo 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-interningWas ist neu in Python 3.8: Änderungen im Python-Verhalten :
quelle