Python-String-Internierung

91

Obwohl diese Frage in der Praxis keinen wirklichen Nutzen hat, bin ich gespannt, wie Python das String-Interning durchführt. Ich habe folgendes bemerkt.

>>> "string" is "string"
True

Das ist wie ich erwartet hatte.

Sie können dies auch tun.

>>> "strin"+"g" is "string"
True

Und das ist ziemlich klug!

Aber das kannst du nicht machen.

>>> s1 = "strin"
>>> s2 = "string"
>>> s1+"g" is s2
False

Warum sollte Python nicht bewerten s1+"g"und erkennen, dass es dasselbe ist s2und auf dieselbe Adresse verweisen? Was ist eigentlich in diesem letzten Block los, damit es zurückkommt False?

Ze'ev G.
quelle

Antworten:

94

Dies ist implementierungsspezifisch, aber Ihr Interpreter interniert wahrscheinlich Konstanten zur Kompilierungszeit, jedoch nicht die Ergebnisse von Laufzeitausdrücken.

Im Folgenden verwende ich CPython 2.7.3.

Im zweiten Beispiel wird der Ausdruck "strin"+"g"zur Kompilierungszeit ausgewertet und durch ersetzt "string". Dadurch verhalten sich die ersten beiden Beispiele gleich.

Wenn wir die Bytecodes untersuchen, werden wir feststellen, dass sie genau gleich sind:

  # s1 = "string"
  2           0 LOAD_CONST               1 ('string')
              3 STORE_FAST               0 (s1)

  # s2 = "strin" + "g"
  3           6 LOAD_CONST               4 ('string')
              9 STORE_FAST               1 (s2)

Das dritte Beispiel beinhaltet eine Laufzeitverkettung, deren Ergebnis nicht automatisch interniert wird:

  # s3a = "strin"
  # s3 = s3a + "g"
  4          12 LOAD_CONST               2 ('strin')
             15 STORE_FAST               2 (s3a)

  5          18 LOAD_FAST                2 (s3a)
             21 LOAD_CONST               3 ('g')
             24 BINARY_ADD          
             25 STORE_FAST               3 (s3)
             28 LOAD_CONST               0 (None)
             31 RETURN_VALUE        

Wenn Sie intern()das Ergebnis des dritten Ausdrucks manuell erstellen würden, würden Sie dasselbe Objekt wie zuvor erhalten:

>>> s3a = "strin"
>>> s3 = s3a + "g"
>>> s3 is "string"
False
>>> intern(s3) is "string"
True
NPE
quelle
21
Und für die Aufnahme: Pythons Guckloch - Optimierung wird im Voraus berechnen arithmetische Operationen mit Konstanten ( "string1" + "s2", 10 + 3*20usw.) bei der Kompilierung, aber Grenzen resultierenden Sequenzen zu nur 20 Elemente (zu verhindern , [None] * 10**1000von allzu Ihre Bytecode erweitert). Es ist diese Optimierung , die kollabiert "strin" + "g"in "string"; Das Ergebnis ist kürzer als 20 Zeichen.
Martijn Pieters
13
Und um es doppelt klar zu machen: Hier findet überhaupt kein Praktikum statt. Unveränderliche Literale werden stattdessen als Konstanten mit dem Bytecode gespeichert. Die Internierung erfolgt für Namen, die im Code verwendet werden, jedoch nicht für vom Programm erstellte Zeichenfolgenwerte, es sei denn, dies wird speziell von der intern()Funktion interniert .
Martijn Pieters
9
Für diejenigen, die versuchen, internFunktion in Python 3 zu finden - es wird nach sys.intern verschoben
Timofey
1

Fall 1

>>> x = "123"  
>>> y = "123"  
>>> x == y  
True  
>>> x is y  
True  
>>> id(x)  
50986112  
>>> id(y)  
50986112  

Fall 2

>>> x = "12"
>>> y = "123"
>>> x = x + "3"
>>> x is y
False
>>> x == y
True

Jetzt ist Ihre Frage , warum die ID gliche in Fall 1 und nicht in Fall 2.
Im Fall 1 haben Sie einen Stringliteral zugewiesen "123"zu xund y.

Da Zeichenfolgen unveränderlich sind, ist es für den Interpreter sinnvoll, das Zeichenfolgenliteral nur einmal zu speichern und alle Variablen auf dasselbe Objekt zu verweisen.
Daher sehen Sie die ID als identisch.

In Fall 2 ändern Sie xmithilfe der Verkettung. Beide xund yhat die gleichen Werte, aber nicht die gleiche Identität.
Beide zeigen auf unterschiedliche Objekte im Speicher. Daher haben sie unterschiedliche idund isOperator zurückgegebenFalse

cppcoder
quelle
Wie kommt es, dass die Zuweisung von x + "3" (und die Suche nach einem neuen Platz zum Speichern der Zeichenfolge) nicht derselben Referenz wie y zugewiesen wird, da Zeichenfolgen unveränderlich sind?
nicecatch
Denn dann muss die neue Zeichenfolge mit allen vorhandenen Zeichenfolgen verglichen werden. möglicherweise eine sehr teure Operation. Es könnte dies im Hintergrund nach der Zuweisung tun, um den Speicher zu reduzieren, aber dann würden Sie ein noch seltsameres Verhalten feststellen: id(x) != id(x)Zum Beispiel, weil die Zeichenfolge während des Evaluierungsprozesses verschoben wurde.
DylanYoung
1
@AndreaConte, da die Verkettung von Zeichenfolgen nicht die zusätzliche Aufgabe erfüllt, bei jeder Generierung einer neuen Zeichenfolge in den Pool aller verwendeten Zeichenfolgen zu schauen. Andererseits "optimiert" der Interpreter den Ausdruck x = "12" + "3"in x = "123"(Verkettung von zwei Zeichenfolgenliteralen in einem einzelnen Ausdruck), sodass die Zuweisung tatsächlich die Suche durchführt und dieselbe "interne" Zeichenfolge wie für findet y = "123".
Ihrio
Tatsächlich ist es nicht so, dass die Zuweisung die Suche durchführt, sondern dass jedes Zeichenfolgenliteral aus dem Quellcode "internalisiert" wird und dieses Objekt an allen anderen Stellen wiederverwendet wird.
Ihrio