Pasante de cadenas de Python

92

Si bien esta pregunta no tiene ningún uso real en la práctica, tengo curiosidad por saber cómo Python realiza prácticas de cadenas. He notado lo siguiente.

>>> "string" is "string"
True

Esto es lo que esperaba.

También puedes hacer esto.

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

¡Y eso es bastante inteligente!

Pero no puedes hacer esto.

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

¿Por qué Python no evaluaría s1+"g"y se daría cuenta de que es lo mismo que s2apuntar a la misma dirección? ¿Qué está pasando realmente en ese último bloque para que vuelva False?

Ze'ev G
fuente

Respuestas:

95

Esto es específico de la implementación, pero su intérprete probablemente esté internando constantes de tiempo de compilación pero no los resultados de las expresiones de tiempo de ejecución.

En lo que sigue utilizo CPython 2.7.3.

En el segundo ejemplo, la expresión "strin"+"g"se evalúa en tiempo de compilación y se reemplaza por "string". Esto hace que los dos primeros ejemplos se comporten igual.

Si examinamos los códigos de bytes, veremos que son exactamente iguales:

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

El tercer ejemplo implica una concatenación en tiempo de ejecución, cuyo resultado no se interna automáticamente:

  # 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        

Si tuviera que obtener manualmente intern()el resultado de la tercera expresión, obtendría el mismo objeto que antes:

>>> s3a = "strin"
>>> s3 = s3a + "g"
>>> s3 is "string"
False
>>> intern(s3) is "string"
True
NPE
fuente
22
Y para que conste: la optimización de mirilla de Python operaciones aritméticas sobre las constantes (va a calcular previamente "string1" + "s2", 10 + 3*20, etc.) en tiempo de compilación, pero los límites resultantes secuencias a sólo 20 elementos (para evitar que [None] * 10**1000se expanda excesivamente su código de bytes). Es esta optimización la que colapsó "strin" + "g"en "string"; el resultado tiene menos de 20 caracteres.
Martijn Pieters
13
Y para dejarlo doblemente claro: no hay prácticas aquí en absoluto. En cambio, los literales inmutables se almacenan como constantes con el código de bytes. Internar no tener lugar para los nombres usados en el código, pero no para los valores de cadena creados por el programa a menos que específicamente internada por la intern()función.
Martijn Pieters
9
Para aquellos que intentan encontrar la internfunción en Python 3, se mueve a sys.intern
Timofey Chernousov
1

Caso 1

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

Caso 2

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

Ahora, su pregunta es por qué la identificación es la misma en el caso 1 y no en el caso 2.
En el caso 1, ha asignado una cadena literal "123"a xy y.

Dado que las cadenas son inmutables, tiene sentido que el intérprete almacene la cadena literal solo una vez y apunte todas las variables al mismo objeto.
Por lo tanto, ve la identificación como idéntica.

En el caso 2, está modificando xmediante concatenación. Ambos xy ytienen los mismos valores, pero no la misma identidad.
Ambos apuntan a diferentes objetos en la memoria. Por lo tanto, han vuelto diferentes idy el isoperadorFalse

cppcoder
fuente
¿Por qué, dado que las cadenas son inmutables, asignar x + "3" (y buscar un nuevo lugar para almacenar la cadena) no se asigna a la misma referencia que y?
nicecatch
Porque entonces necesita comparar la nueva cadena con todas las cadenas existentes; potencialmente una operación muy cara. Supongo que podría hacer esto en segundo plano después de la asignación, para reducir la memoria, pero luego terminaría con un comportamiento aún más extraño: id(x) != id(x)por ejemplo, porque la cadena se movió en el proceso de evaluación.
DylanYoung
1
@AndreaConte porque la concatenación de cadenas no hace el trabajo adicional de buscar en el grupo de todas las cadenas usadas cada vez que genera una nueva. Por otro lado, el intérprete "optimiza" la expresión x = "12" + "3"en x = "123"(concatenación de dos literales de cadena en una sola expresión) para que la asignación realmente realice la búsqueda y encuentre la misma cadena "interna" que para y = "123".
derenio
En realidad, no es esa asignación la que realiza la búsqueda, sino que cada literal de cadena del código fuente se "internaliza" y ese objeto se reutiliza en todos los demás lugares.
derenio