Acerca del cambio de identificación de una cadena inmutable
Algo sobre el id
tipo de objetos str
(en Python 2.7) me desconcierta. El str
tipo es inmutable, por lo que esperaría que una vez creado, siempre tenga el mismo archivo id
. Creo que no me expreso tan bien, así que publicaré un ejemplo de secuencia de entrada y salida.
>>> id('so')
140614155123888
>>> id('so')
140614155123848
>>> id('so')
140614155123808
Mientras tanto, cambia todo el tiempo. Sin embargo, después de tener una variable apuntando a esa cadena, la cosa cambia:
>>> 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 mantiene ese valor. De hecho, después del so
y del not_so
, la salida de id('so')
comienza a cambiar nuevamente.
Este no es el mismo comportamiento que con los números enteros (pequeños).
Sé que no existe una conexión real entre la inmutabilidad y tener lo mismo 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 se sorprendería menos que yo, así que estoy tratando de llegar al mismo punto...
Actualizar
Probar lo mismo con una cuerda diferente dio resultados diferentes...
>>> id('hello')
139978087896384
>>> id('hello')
139978087896384
>>> id('hello')
139978087896384
Ahora es igual...
CPython no promete internar todas las cadenas de forma predeterminada, pero en la práctica, muchos lugares en el código base de Python reutilizan objetos de cadena ya creados. Muchas funciones internas de Python usan (el equivalente en C) la sys.intern()
llamada a función para internar explícitamente cadenas de Python, pero a menos que se encuentre uno de esos casos especiales, dos literales de cadenas de Python idénticos producirán cadenas diferentes.
Python también es libre de reutilizar ubicaciones de memoria, y Python también optimizará los literales inmutables almacenándolos una vez, en el momento de la compilación, con el código de bytes en los objetos de código. El REPL (intérprete interactivo) de Python también almacena el resultado de la expresión más reciente en el _
nombre, lo que confunde un poco más las cosas.
Como tal, verá aparecer la misma identificación de vez en cuando.
Ejecutar solo la línea id(<string literal>)
en REPL pasa por varios pasos:
Se compila la línea, lo que incluye la creación de una constante para el objeto de cadena:
>>> compile("id('foo')", '<stdin>', 'single').co_consts ('foo', None)
Esto muestra las constantes almacenadas con el código de bytes compilado; en este caso una cadena
'foo'
y elNone
singleton. Las expresiones simples que consisten en que producen un valor inmutable se pueden optimizar en esta etapa; consulte la nota sobre optimizadores a continuación.Durante la ejecución, la cadena se carga desde las constantes del código y
id()
devuelve la ubicación de la memoria. Elint
valor resultante está vinculado a_
, además de 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
Nada hace referencia al objeto de código, 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.
Entonces, Python quizás pueda 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 la identificación no es predecible; Si mientras tanto el recolector de basura se ejecuta para borrar las referencias circulares, se podría liberar otra memoria y obtendrá nuevas direcciones de memoria.
A continuación, el compilador de Python también internará cualquier cadena de Python almacenada como una constante, siempre que se parezca lo suficiente a 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, llamando a intern_string_constants()
. Esta función se repite a través de las estructuras de constantes y para cualquier objeto de cadena v
que se encuentre allí se ejecuta:
if (all_name_chars(v)) {
PyObject *w = v;
PyUnicode_InternInPlace(&v);
if (w != v) {
PyTuple_SET_ITEM(tuple, i, v);
modified = 1;
}
}
donde all_name_chars()
está documentado como
/* all_name_chars(s): true iff s matches [a-zA-Z0-9_]* */
Como creó cadenas que se ajustan a ese criterio, están internadas, por lo que ve que se usa el mismo ID para la 'so'
cadena en su segunda prueba: mientras sobreviva una referencia a la versión internada, la internación hará que futuros 'so'
literales reutilicen la objeto de cadena internado, incluso en nuevos bloques de código y vinculado 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ás creando un global cuyo nombre y valor son iguales. Como Python interna tanto identificadores como constantes de calificación, terminas usando el mismo objeto de cadena tanto para el identificador como para 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 objetos de código o contienen caracteres fuera del rango de letras + números + guión bajo, verá que el id()
valor 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 compilador de Python utiliza el optimizador de mirilla (versiones de Python <3.7) o el optimizador AST más capaz (3.7 y posteriores) para precalcular (doblar) los resultados de expresiones simples que involucran constantes. El peepholder limita su salida a una secuencia de longitud 20 o menos (para evitar objetos de código inflados y uso de memoria), mientras que el optimizador AST usa un límite separado para cadenas de 4096 caracteres. Esto significa que la concatenación de cadenas más cortas que constan únicamente de caracteres de nombre aún puede generar cadenas internas si la cadena resultante se ajusta a los límites del optimizador de su versión actual de Python.
Por ejemplo, en Python 3.7, 'foo' * 20
el resultado será una única cadena interna, porque el plegado constante lo convierte en un valor único, mientras que en Python 3.6 o anterior solo 'foo' * 6
se plegará:
>>> import dis, sys
>>> sys.version_info
sys.version_info(major=3, minor=7, micro=4, releaselevel='final', serial=0)
>>> dis.dis("'foo' * 20")
1 0 LOAD_CONST 0 ('foofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoo')
2 RETURN_VALUE
y
>>> dis.dis("'foo' * 6")
1 0 LOAD_CONST 2 ('foofoofoofoofoofoo')
2 RETURN_VALUE
>>> dis.dis("'foo' * 7")
1 0 LOAD_CONST 0 ('foo')
2 LOAD_CONST 1 (7)
4 BINARY_MULTIPLY
6 RETURN_VALUE
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 lo ejecuto, recibo el siguiente resultado:
2888960 2888960 2888960
En CPython, un literal de cadena se trata como una constante, lo que podemos ver en el código de bytes 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.
'so'
En su primer ejemplo , cada vez se crea una nueva instancia de la cadena , por lo tanto, una identificación diferente.
En el segundo ejemplo, vincula la cadena a una variable y Python puede mantener una copia compartida de la cadena.