tipos - que es una tupla en python
Acerca de la identificación cambiante de una cadena inmutable (5)
Algo sobre la id
de objetos de tipo str
(en python 2.7) me desconcierta. El tipo str
es inmutable, por lo que esperaría que una vez creado, siempre tenga la misma id
. Creo que no me expreso tan bien, así que en su lugar publicaré un ejemplo de secuencia de entrada y salida.
>>> id(''so'')
140614155123888
>>> id(''so'')
140614155123848
>>> id(''so'')
140614155123808
así que mientras tanto, cambia todo el tiempo. Sin embargo, después de tener una variable apuntando a esa cadena, las cosas cambian:
>>> so = ''so''
>>> id(''so'')
140614155123728
>>> so = ''so''
>>> id(so)
140614155123728
>>> not_so = ''so''
>>> id(not_so)
140614155123728
Entonces parece que congela la identificación, una vez que una variable tiene ese valor. De hecho, después de del so
y del not_so
, la salida de id(''so'')
comienza a cambiar nuevamente.
Este no es el mismo comportamiento que con enteros (pequeños).
Sé que no existe una conexión real entre la inmutabilidad y tener la misma id
; Aún así, estoy tratando de descubrir la fuente de este comportamiento. Creo que alguien que esté familiarizado con las partes internas de Python estaría menos sorprendido que yo, así que estoy tratando de llegar al mismo punto ...
Actualizar
Probar lo mismo con una cadena diferente dio resultados diferentes ...
>>> id(''hello'')
139978087896384
>>> id(''hello'')
139978087896384
>>> id(''hello'')
139978087896384
Ahora es igual ...
CPython no promete cadenas internas de forma predeterminada, pero en la práctica, muchos lugares en la base de código de Python reutilizan objetos de cadenas ya creados. Muchos internos de Python usan (el C-equivalente de) la llamada a la función intern()
para interponer explícitamente cadenas de Python, pero a menos que aciertes uno de esos casos especiales, dos literales de cadena de Python idénticos producirán diferentes cadenas.
Python también es libre de reutilizar ubicaciones de memoria, y Python también optimizará literales inmutables almacenándolos una vez, en tiempo de compilación, con el bytecode en objetos de código. El Python REPL (intérprete interactivo) también almacena el resultado de expresión más reciente en el nombre _
, lo que complica un poco más las cosas.
Como tal, verá la misma aparición de id de vez en cuando.
Ejecutar solo el id(<string literal>)
línea id(<string literal>)
en el REPL pasa por varios pasos:
La línea está compilada, lo que incluye crear una constante para el objeto de cadena:
>>> compile("id(''foo'')", ''<stdin>'', ''single'').co_consts (''foo'', None)
Esto muestra las constantes almacenadas con el bytecode compilado; en este caso, una cadena
''foo''
yNone
singleton.En la ejecución, la cadena se carga desde las constantes de código, y
id()
devuelve la ubicación de la memoria. El valorint
resultante está obligado a_
, así como impreso:>>> import dis >>> dis.dis(compile("id(''foo'')", ''<stdin>'', ''single'')) 1 0 LOAD_NAME 0 (id) 3 LOAD_CONST 0 (''foo'') 6 CALL_FUNCTION 1 9 PRINT_EXPR 10 LOAD_CONST 1 (None) 13 RETURN_VALUE
El objeto de código no se hace referencia a nada, el recuento de referencias cae a 0 y el objeto de código se elimina. Como consecuencia, también lo es el objeto de cadena.
Python puede entonces reutilizar la misma ubicación de memoria para un nuevo objeto de cadena, si vuelve a ejecutar el mismo código. Esto generalmente lleva a que se imprima la misma dirección de memoria si repite este código. Esto depende de qué más hagas con tu memoria Python .
La reutilización de ID no es predecible; si mientras tanto el recolector de basura se ejecuta para borrar las referencias circulares, se puede liberar otra memoria y obtendrá nuevas direcciones de memoria.
A continuación, el compilador de Python interpondrá también cualquier cadena de Python almacenada como una constante, siempre que se vea lo suficiente como un identificador válido. La función de fábrica de objetos de código Python PyCode_New internará cualquier objeto de cadena que contenga solo letras, dígitos o guiones bajos ASCII:
/* Intern selected string constants */
for (i = PyTuple_Size(consts); --i >= 0; ) {
PyObject *v = PyTuple_GetItem(consts, i);
if (!PyString_Check(v))
continue;
if (!all_name_chars((unsigned char *)PyString_AS_STRING(v)))
continue;
PyString_InternInPlace(&PyTuple_GET_ITEM(consts, i));
}
Como ha creado cadenas que se ajustan a ese criterio, están internados, por lo que ve que se usa la misma ID para la cadena ''so''
en su segunda prueba: mientras sobreviva una referencia a la versión interna, la internación causará el futuro ''so''
literales para reutilizar el objeto de cadena interna, incluso en nuevos bloques de código y vinculados a diferentes identificadores. En su primera prueba, no guarda una referencia a la cadena, por lo que las cadenas internas se descartan antes de que puedan reutilizarse.
Por cierto, su nuevo nombre so = ''so''
vincula una cadena a un nombre que contiene los mismos caracteres . En otras palabras, está creando un global cuyo nombre y valor son iguales. Dado que Python interna tanto los identificadores como las constantes calificativas, terminas usando el mismo objeto de cadena para el identificador y su valor:
>>> compile("so = ''so''", ''<stdin>'', ''single'').co_names[0] is compile("so = ''so''", ''<stdin>'', ''single'').co_consts[0]
True
Si crea cadenas que no son constantes de objeto de código o contienen caracteres fuera de las letras + números + rango de subrayado, verá que el valor id()
no se reutiliza:
>>> some_var = ''Look ma, spaces and punctuation!''
>>> some_other_var = ''Look ma, spaces and punctuation!''
>>> id(some_var)
4493058384
>>> id(some_other_var)
4493058456
>>> foo = ''Concatenating_'' + ''also_helps_if_long_enough''
>>> bar = ''Concatenating_'' + ''also_helps_if_long_enough''
>>> foo is bar
False
>>> foo == bar
True
El optimizador de mirilla de Python calcula previamente los resultados de las expresiones simples, pero si da como resultado una secuencia de más de 20, se ignora la salida (para evitar el uso de objetos de código y memoria hinchables); por lo tanto, la concatenación de cadenas más cortas que solo contengan caracteres de nombre puede conducir a cadenas internas si el resultado es de 20 caracteres o menos.
En su primer ejemplo ''so''
se crea una nueva instancia de la cadena ''so''
cada vez, por lo tanto, una identificación diferente.
En el segundo ejemplo, está vinculando la cadena a una variable y Python puede mantener una copia compartida de la cadena.
Este comportamiento es específico del shell interactivo de Python. Si pongo lo siguiente en un archivo .py:
print id(''so'')
print id(''so'')
print id(''so'')
y ejecutarlo, recibo el siguiente resultado:
2888960 2888960 2888960
En CPython, un literal de cadena se trata como una constante, que podemos ver en el bytecode del fragmento anterior:
2 0 LOAD_GLOBAL 0 (id)
3 LOAD_CONST 1 (''so'')
6 CALL_FUNCTION 1
9 PRINT_ITEM
10 PRINT_NEWLINE
3 11 LOAD_GLOBAL 0 (id)
14 LOAD_CONST 1 (''so'')
17 CALL_FUNCTION 1
20 PRINT_ITEM
21 PRINT_NEWLINE
4 22 LOAD_GLOBAL 0 (id)
25 LOAD_CONST 1 (''so'')
28 CALL_FUNCTION 1
31 PRINT_ITEM
32 PRINT_NEWLINE
33 LOAD_CONST 0 (None)
36 RETURN_VALUE
La misma constante (es decir, el mismo objeto de cadena) se carga 3 veces, por lo que los ID son los mismos.
Por lo tanto, aunque no se garantiza que Python interne cadenas, con frecuencia reutilizará la misma cadena, y puede inducir a error. Es importante saber que no debe verificar el id
o is
para igualdad de cadenas.
Para demostrar esto, una manera que he descubierto para forzar una nueva cadena en Python 2.6 al menos:
>>> so = ''so''
>>> new_so = ''{0}''.format(so)
>>> so is new_so
False
y aquí hay un poco más de exploración de Python:
>>> id(so)
102596064
>>> id(new_so)
259679968
>>> so == new_so
True
Una forma más simplificada de entender el comportamiento es verificar los siguientes tipos de datos y variables .
La sección "Pecularity String" ilustra su pregunta usando caracteres especiales como ejemplo.