9

Ich stieß auf ein verwirrendes Problem beim Unit-Testen eines Moduls. Das Modul wirft tatsächlich Werte und ich möchte diese Werte vergleichen.'ist' Operator verhält sich unerwartet mit Schwimmer

Es gibt einen Unterschied im Vergleich zu == und is (zum Teil, ich bin der Unterschied Vorsicht)

>>> 0.0 is 0.0 
True # as expected 
>>> float(0.0) is 0.0 
True # as expected 

Wie bisher erwartet, aber hier ist mein "Problem":

>>> float(0) is 0.0 
False 
>>> float(0) is float(0) 
False 

Warum? Zumindest ist der letzte wirklich verwirrend für mich. Die interne Darstellung von float(0) und float(0.0) sollte gleich sein. Der Vergleich mit == funktioniert wie erwartet.

+2

In Verbindung stehende: http://stackoverflow.com/questions/132988/is-there-a-difference-between-and-is-in-python – Elazar

+1

Ihre Frage verdient eine Antwort, aber wenn Sie auf dieses Problem stoßen in echter Code, der Code ist wahrscheinlich fehlerhaft und sollte behoben werden. Es gibt (fast) keinen Grund, die Referenzidentität zwischen Schwimmern so zu testen. – Elazar

+1

Die seltsame Sache ist, dass, obwohl ich dies reproduzieren kann, alle von 'id (0.0) ',' id (float (0.0)) 'und' id (float (0)) 'den gleichen Wert zurückgeben. ... Das heißt, der Wert ist der gleiche, wenn ich diese nacheinander in der interaktiven Shell ausfühle, aber wenn ich 'id (float (0.0)), id (float (0))' (als Tupel) mache dann unterscheiden sich die IDs. Irgendeine Erklärung? –

Antwort

20

Dies hat damit zu tun, wie is funktioniert. Es wird nach Referenzen statt nach Wert gesucht. Es gibt True zurück, wenn eines der Argumente demselben Objekt zugewiesen ist.

In diesem Fall sind sie verschiedene Instanzen; float(0) und float(0) haben den gleichen Wert ==, sind aber, was Python betrifft, unterschiedliche Entitäten. Die CPython-Implementierung speichert außerdem ganze Zahlen als Singleton-Objekte in diesem Bereich ->[x | x ∈ z ∧ -5 ≤ x ≤ 256]:

>>> 0.0 is 0.0 
True 
>>> float(0) is float(0) # Not the same reference, unique instances. 
False 

In diesem Beispiel haben wir das ganze Zahl Caching-Prinzip nachweisen können:

>>> a = 256 
>>> b = 256 
>>> a is b 
True 
>>> a = 257 
>>> b = 257 
>>> a is b 
False 

Nun, wenn Schwimmer zu float() übergeben werden, die float literal wird einfach zurückgegeben (short-circuited), da in der gleichen Referenz verwendet wird, da kein neuer Float aus einem vorhandenen Float instanziiert werden muss:

Dies kann weiter demonstriert werden durch die Verwendung int() auch:

>>> int(256.0) is int(256.0) # Same reference, cached. 
True 
>>> int(257.0) is int(257.0) # Different references are returned, not cached. 
False 
>>> 257 is 257 # Same reference. 
True 
>>> 257.0 is 257.0 # Same reference. As @Martijn Pieters pointed out. 
True 

jedoch die Ergebnisse der is sind auch abhängig von dem Umfang es (über die Spannweite dieser Frage/Erklärung) in gerade ausgeführt wird Bitte beziehen Sie sich auf Benutzer: @Jim 's fantastische Erklärung auf code objects. Auch Python enthält zu diesem Verhalten einen Abschnitt doc:

[7] Durch die automatische Garbage-Collection, freie Listen und die dynamische Natur von Deskriptoren, können Sie bemerke scheinbar ungewöhnliches Verhalten in bestimmten Anwendungen des is-Operators, wie diejenigen, die Vergleiche zwischen Instanzmethoden oder Konstanten beinhalten. Überprüfen Sie ihre Dokumentation für weitere Informationen.

+0

'float (17.0) ist float (17.0)' gibt 'True' zurück, aber' float (17.0) ist float ('17.0 ') 'returns' False' -> ist 'float (some_value)' gibt nur den ursprünglichen float zurück Es wird eine neue Instanz erstellt (falls ein Wert ein Float ist ...)? –

+0

@Jim antwortete auf diese –

+0

CPython Cache schwimmt nicht. Sie sehen eine Kombination aus konstanter Faltung und dem Float-Konstruktor, der die Fließkommawerte für die Fälle, in denen "ist", "True" ergibt, direkt durchläuft. – user2357112

9

Wenn ein float Objekt float() geliefert wird, CPython * gibt es nur, ohne ein neues Objekt zu machen.

Dies kann in PyNumber_Float zu sehen, wo das Objekt o mit PyFloat_CheckExact geprüft bestanden (die schließlich von float_new genannt wird); wenn True, es erhöht nur den Referenzzähler und gibt ihr:

if (PyFloat_CheckExact(o)) { 
    Py_INCREF(o); 
    return o; 
} 

Als Ergebnis der id des Objekts gleich bleibt. So ist der Ausdruck

>>> float(0.0) is float(0.0) 

reduziert sich auf:

>>> 0.0 is 0.0 

Aber warum tut das gleiche True? Nun, CPython hat einige kleine Optimierungen.

In diesem Fall verwendet es das gleiche Objekt für die beiden Vorkommen von 0.0 in Ihrem Befehl, weil sie Teil von the same code object sind (kurzer Haftungsausschluss: sie sind auf der gleichen logischen Linie); so wird der is Test erfolgreich sein.

Dies kann weiter, wenn Sie float(0.0) in getrennten Leitungen (oder begrenzt durch ;) und dann Prüfung für Identität ausführen erhärtet werden:

a = float(0.0); b = float(0.0) # Python compiles these separately 
a is b # False 

Auf der anderen Seite, wenn ein int (oder ein str) geliefert wird, erstellt CPython ein neuesfloat Objekt daraus und gibt das zurück. Dazu verwendet es PyFloat_FromDouble bzw. PyFloat_FromString.

Der Effekt ist, dass die zurückgegebenen Objekte in id s unterscheiden (die Identitäten überprüft, mit is):

# Python uses the same object representing 0 to the calls to float 
# but float returns new float objects when supplied with ints 
# Thereby, the result will be False 
float(0) is float(0) 

* Hinweis: Alle bisherigen erwähnten Verhalten für die Umsetzung gilt der Python in C dh CPython. Andere Implementierungen weisen möglicherweise ein anderes Verhalten auf. Kurz gesagt, nicht davon abhängen.

+0

Thx für Ihre Antwort mit Ihren Details, obwohl ich die Antwort von mirdomoboto als richtig akzeptieren werde (Ich hasse diese harten Entscheidungen ...) –

+1

Ich habe Ihren Teil auf Code-Objekte als Fußnote hinzugefügt (großartige Erklärung übrigens!). – ospahiu