Der „is“-Operator verhält sich bei nicht zwischengespeicherten Ganzzahlen unerwartet

Lesezeit: 7 Minuten

Der „is Operator verhalt sich bei nicht zwischengespeicherten Ganzzahlen unerwartet
Dimitris Fasarakis Hilliard

Beim Herumspielen mit dem Python-Interpreter bin ich auf diesen widersprüchlichen Fall bezüglich der gestoßen is Operator:

Wenn die Auswertung in der Funktion erfolgt, wird sie zurückgegeben Truewenn es draußen gemacht wird, kehrt es zurück False.

>>> def func():
...     a = 1000
...     b = 1000
...     return a is b
...
>>> a = 1000
>>> b = 1000
>>> a is b, func()
(False, True)

Seit der is Betreiber wertet die aus id()‘s für die beteiligten Objekte bedeutet dies a und b weisen auf dasselbe hin int Instanz, wenn sie innerhalb der Funktion deklariert wird func aber im Gegenteil, sie zeigen auf einen anderen Gegenstand, wenn sie sich außerhalb desselben befinden.

Warum ist das so?


Notiz: Mir ist der Unterschied zwischen Identität (is) und Gleichheit (==)-Operationen, wie in Pythons „is“-Operator verstehen beschrieben. Darüber hinaus ist mir auch das Caching bewusst, das Python für die Ganzzahlen im Bereich durchführt [-5, 256] wie unter “ist” beschrieben, verhält sich der Operator bei ganzen Zahlen unerwartet.

Dies ist hier nicht der Fall da die Zahlen außerhalb dieses Bereichs liegen und ich mache Identität auswerten wollen und nicht Gleichstellung.

  • Die Definition von Python, der Sprache, garantiert, dass die Singletons None, False und True sie selbst sind und dass mehrere Instanzen veränderlicher Bultin-Klassen nicht unterschiedlich sind. Die Existenz mehrerer Instanzen unveränderlicher eingebauter Klassen mit demselben Wert ist abhängig von Wert, Version und Implementierung. Ich nehme an, dass Sie mit “dem Python-Interpreter” CPython meinen. Mit anderen Interpretern erhalten Sie möglicherweise andere Ergebnisse. Mit CPython erhalten Sie für “kleine” int-Werte ein anderes Ergebnis. Versuchen Sie es mit 250 statt 1000. Bei älteren Versionen von CPython erhalten Sie möglicherweise ein anderes Ergebnis.

    – Terry Jan Reedy

    8. Dezember 15 um 10:44 Uhr


  • Warum interessiert Sie das? Verwenden is auf ganzen Zahlen fühlt sich für mich falsch an.

    – Martin Bonner unterstützt Monika

    5. September 16 um 7:49 Uhr

  • @MartinBonner Ich interessiere mich hauptsächlich dafür, wie CPython implementiert wird. Ich bin auf dieses Verhalten gestoßen, habe es untersucht und beschlossen, ein Q & A zu posten, weil ich dachte, dass andere es auch interessant finden könnten. Es ist falsch, ich schlage nicht vor, es zu benutzen 😉

    – Dimitris Fasarakis Hilliard

    5. September 16 um 9:02 Uhr

Der „is Operator verhalt sich bei nicht zwischengespeicherten Ganzzahlen unerwartet
Dimitris Fasarakis Hilliard

tl;dr:

Als die Referenzhandbuch Zustände:

Ein Block ist ein Stück Python-Programmtext, der als Einheit ausgeführt wird. Das Folgende sind Blöcke: ein Modul, ein Funktionskörper und eine Klassendefinition.
Jeder interaktiv eingegebene Befehl ist ein Block.

Deshalb haben Sie im Fall einer Funktion a Single Codeblock, der eine enthält Single Objekt für das numerische Literal
1000Also id(a) == id(b) wird nachgeben True.

Im zweiten Fall haben Sie zwei unterschiedliche Codeobjekte jede mit ihrem eigenen unterschiedlichen Objekt für das Literal 1000 Also id(a) != id(b).

Beachten Sie, dass sich dieses Verhalten nicht mit manifestiert int nur Literale, erhalten Sie ähnliche Ergebnisse beispielsweise mit float Literale (siehe hier).

Das Vergleichen von Objekten (außer explizit is None tests ) sollte immer mit dem Gleichheitsoperator durchgeführt werden == und nicht is.

Alles, was hier gesagt wird, gilt für die beliebteste Implementierung von Python, CPython. Andere Implementierungen können abweichen, daher sollten bei ihrer Verwendung keine Annahmen getroffen werden.


Längere Antwort:

Um dies etwas klarer zu sehen und zusätzlich zu verifizieren scheinbar seltsam Verhalten können wir uns direkt anschauen code Objekte für jeden dieser Fälle mit der dis Modul.

Für die Funktion func:

Funktionsobjekte haben neben allen anderen Attributen auch ein __code__ -Attribut, mit dem Sie den kompilierten Bytecode für diese Funktion einsehen können. Verwenden dis.code_info Wir können eine nette hübsche Ansicht aller gespeicherten Attribute in einem Codeobjekt für eine bestimmte Funktion erhalten:

>>> print(dis.code_info(func))
Name:              func
Filename:          <stdin>
Argument count:    0
Kw-only arguments: 0
Number of locals:  2
Stack size:        2
Flags:             OPTIMIZED, NEWLOCALS, NOFREE
Constants:
   0: None
   1: 1000
Variable names:
   0: a
   1: b

Uns interessiert nur die Constants Eintrag für Funktion func. Darin können wir sehen, dass wir zwei Werte haben, None (immer vorhanden) und 1000. Wir haben nur eine Single int-Instanz, die die Konstante darstellt 1000. Das ist der Wert, der a und b zugewiesen werden, wenn die Funktion aufgerufen wird.

Der Zugriff auf diesen Wert ist einfach über func.__code__.co_consts[1] und so eine andere Möglichkeit, unsere anzuzeigen a is b Auswertung in der Funktion wäre so:

>>> id(func.__code__.co_consts[1]) == id(func.__code__.co_consts[1]) 

Was natürlich zu bewerten ist True weil wir uns auf dasselbe Objekt beziehen.

Für jeden interaktiven Befehl:

Wie bereits erwähnt, wird jeder interaktive Befehl als einzelner Codeblock interpretiert: getrennt geparst, kompiliert und ausgewertet.

Wir können die Codeobjekte für jeden Befehl über die abrufen compile eingebaut:

>>> com1 = compile("a=1000", filename="", mode="single")
>>> com2 = compile("b=1000", filename="", mode="single")

Für jede Zuweisungsanweisung erhalten wir ein ähnlich aussehendes Codeobjekt, das wie folgt aussieht:

>>> print(dis.code_info(com1))
Name:              <module>
Filename:          
Argument count:    0
Kw-only arguments: 0
Number of locals:  0
Stack size:        1
Flags:             NOFREE
Constants:
   0: 1000
   1: None
Names:
   0: a

Der gleiche Befehl für com2 sieht aber gleich aus hat einen grundlegenden Unterschied: jedes der Codeobjekte com1 und com2 haben verschiedene int-Instanzen, die das Literal darstellen 1000. Deshalb, in diesem Fall, wenn wir es tun a is b über die co_consts Argument erhalten wir tatsächlich:

>>> id(com1.co_consts[0]) == id(com2.co_consts[0])
False

Was mit dem übereinstimmt, was wir tatsächlich bekommen haben.

Unterschiedliche Codeobjekte, unterschiedliche Inhalte.


Notiz: Ich war etwas neugierig, wie genau das im Quellcode passiert, und nachdem ich ihn durchwühlt habe, glaube ich, ihn endlich gefunden zu haben.

Während der Kompilierungsphase wird die co_consts -Attribut wird durch ein Dictionary-Objekt dargestellt. In compile.c wir können tatsächlich die Initialisierung sehen:

/* snippet for brevity */

u->u_lineno = 0;
u->u_col_offset = 0;
u->u_lineno_set = 0;
u->u_consts = PyDict_New();  

/* snippet for brevity */

Beim Kompilieren wird auf bereits vorhandene Konstanten geprüft. Siehe die Antwort von @Raymond Hettinger unten für ein bisschen mehr dazu.


Vorbehalte:

  • Verkettete Anweisungen werden zu einer Identitätsprüfung ausgewertet True

    Es sollte nun klarer werden, warum genau das Folgende zu werten ist True:

     >>> a = 1000; b = 1000;
     >>> a is b
    

    In diesem Fall teilen wir dem Interpreter durch Verketten der beiden Zuweisungsbefehle mit, diese zu kompilieren zusammen. Wie beim Funktionsobjekt nur ein Objekt für das Literal 1000 wird erstellt, was zu a führt True Wert bei Auswertung.

  • Die Ausführung auf Modulebene ergibt True wieder:

    Wie bereits erwähnt, heißt es im Referenzhandbuch:

    … Folgendes sind Blöcke: ein Modul

    Es gilt also dieselbe Prämisse: Wir haben ein einzelnes Codeobjekt (für das Modul) und als Ergebnis werden einzelne Werte für jedes unterschiedliche Literal gespeichert.

  • Das gleiche nicht bewerben für veränderlich Objekte:

Das heißt, es sei denn, wir initialisieren explizit auf dasselbe veränderliche Objekt (z. B. mit a = b = []), wird die Identität der Objekte niemals gleich sein, zum Beispiel:

    a = []; b = []
    a is b  # always evaluates to False

Wieder rein die Dokumentationdas ist angegeben:

nach a = 1; b = 1, a und b können sich je nach Implementierung auf dasselbe Objekt mit dem Wert eins beziehen oder nicht, aber nach c = []; d = []c und d beziehen sich garantiert auf zwei verschiedene, eindeutige, neu erstellte leere Listen.

1644148029 85 Der „is Operator verhalt sich bei nicht zwischengespeicherten Ganzzahlen unerwartet
Raymond Hettinger

An der interaktiven Eingabeaufforderung sind die Eingaben zusammengestellt in einem Single Modus die jeweils eine vollständige Anweisung verarbeitet. Der Compiler selbst (in Python/compile.c) verfolgt die Konstanten in einem Wörterbuch namens u_consts die das konstante Objekt seinem Index zuordnet.

Im compiler_add_o() -Funktion sehen Sie, dass vor dem Hinzufügen einer neuen Konstante (und dem Erhöhen des Indexes) das Diktat überprüft wird, um festzustellen, ob das Konstantenobjekt und der Index bereits vorhanden sind. Wenn ja, werden sie wiederverwendet.

Kurz gesagt bedeutet dies, dass sich wiederholende Konstanten in einer Anweisung (z. B. in Ihrer Funktionsdefinition) zu einem Singleton gefaltet werden. Im Gegensatz dazu Ihre a = 1000 und b = 1000 sind zwei getrennte Anweisungen, es findet also keine Faltung statt.

FWIW, das ist alles nur ein CPython-Implementierungsdetail (dh nicht durch die Sprache garantiert). Aus diesem Grund beziehen sich die hier angegebenen Verweise auf den C-Quellcode und nicht auf die Sprachspezifikation, die keine Garantien zu diesem Thema gibt.

Ich hoffe, Ihnen hat dieser Einblick gefallen, wie CPython unter der Haube funktioniert 🙂

  • Danke, eine maßgebliche Antwort, um zu überprüfen, was ich geschrieben habe, war dringend erforderlich (und ich brauche auch einen Ort, an dem ich das Kopfgeld vergeben kann 🙂

    – Dimitris Fasarakis Hilliard

    5. September 16 um 7:34 Uhr


  • @Jim Helfen gerne. Hin und wieder lauert ein Python-Core-Entwickler auf StackOverflow und kann Sie direkt zum Kern der Sache bringen.

    – Raymond Hettinger

    5. September 16 um 7:36 Uhr

.

796600cookie-checkDer „is“-Operator verhält sich bei nicht zwischengespeicherten Ganzzahlen unerwartet

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy