¿Cómo puede una cadena no asignada en Python tener una dirección en la memoria?
memory-address (5)
El código que publicó crea nuevas cadenas como objetos intermedios. Estas cadenas creadas eventualmente tienen los mismos contenidos que sus originales. En el período de tiempo intermedio, no coinciden exactamente con el original, y deben mantenerse en una dirección distinta.
>>> id(''cat'')
5181152
Como otros han respondido, al emitir estas instrucciones, hace que Python VM cree un objeto de cadena que contenga la cadena "cat". Este objeto de cadena está en caché y está en la dirección 5181152.
>>> a = ''cat''
>>> id(a)
5181152
De nuevo, se ha asignado a a para referirse a este objeto de cadena en caché en 5181152, que contiene "cat".
>>> a = a[0:2]
>>> id(a)
27731511
En este punto de mi versión modificada de tu programa, has creado dos pequeños objetos de cadena: ''cat''
y ''ca''
. ''cat''
todavía existe en el caché. La cadena a la que a
refiere es un objeto de cadena diferente y probablemente novedoso, que contiene los caracteres ''ca''
.
>>> a = a + ''t''
>>> id(a)
39964224
Ahora ha creado otro nuevo objeto de cadena. Este objeto es la concatenación de la cadena ''ca''
en la dirección 27731511, y la cadena ''t''
. Esta concatenación no coincide con la cadena previamente almacenada en caché ''cat''
. Python no detecta automáticamente este caso. Como lo indica kindall, puede forzar la búsqueda con el método intern()
.
Esperemos que esta explicación ilumine los pasos por los cuales la dirección de a
cambió.
Su código no incluía el estado intermedio con a
cadena asignada ''ca''
. La respuesta todavía se aplica, porque el intérprete de Python genera un nuevo objeto de cadena para mantener el resultado intermedio a[0:2]
, ya sea que asigne ese resultado intermedio a una variable o no.
¿Alguien me puede explicar esto? Así que he estado jugando con el comando id () en python y me encontré con esto:
>>> id(''cat'')
5181152
>>> a = ''cat''
>>> b = ''cat''
>>> id(a)
5181152
>>> id(b)
5181152
Esto tiene sentido para mí, excepto por una parte: la cadena ''cat'' tiene una dirección en la memoria antes de asignarla a una variable. Probablemente no entiendo cómo funciona el direccionamiento de memoria, pero ¿alguien puede explicarme esto o al menos decirme que debo leer sobre el direccionamiento de memoria?
Así que eso está muy bien, pero esto me confundió aún más:
>>> a = a[0:2]+''t''
>>> a
''cat''
>>> id(a)
39964224
>>> id(''cat'')
5181152
Esto me pareció raro porque ''cat'' es una cadena con una dirección de 5181152 pero la nueva a tiene una dirección diferente. Entonces, si hay dos cadenas ''cat'' en la memoria, ¿por qué no se imprimen dos direcciones para id (''cat'') ? Mi último pensamiento fue que la concatenación tenía algo que ver con el cambio de dirección, así que probé esto:
>>> id(b[0:2]+''t'')
39921024
>>> b = b[0:2]+''t''
>>> b
''cat''
>>> id(b)
40000896
Hubiera predicho que las identificaciones serían las mismas, pero ese no era el caso. ¿Pensamientos?
Las variables de Python son bastante diferentes de las variables en otros idiomas (por ejemplo, C).
En muchos otros idiomas, una variable es un nombre para una ubicación en la memoria. En estos idiomas, diferentes tipos de variables pueden referirse a diferentes tipos de ubicaciones, y la misma ubicación podría recibir varios nombres. En su mayor parte, una ubicación de memoria determinada puede hacer que los datos cambien de vez en cuando. También hay formas de referirse indirectamente a las ubicaciones de memoria ( int *p
contendría la dirección, y en la ubicación de la memoria en esa dirección, hay un entero). Pero una ubicación real a la que las referencias de una variable no pueden cambiar; La variable es la ubicación. Una asignación variable en estos idiomas es efectivamente "Buscar la ubicación de esta variable y copiar esta información en esa ubicación"
Python no funciona de esa manera. En python, los objetos reales entran en alguna ubicación de la memoria, y las variables son como etiquetas para las ubicaciones. Python gestiona los valores almacenados de forma separada de cómo gestiona las variables. Esencialmente, una tarea en python significa "Buscar la información para esta variable, olvidar la ubicación a la que ya se refiere y reemplazarla con esta nueva ubicación". No se copian datos
Una característica común de los lenguajes que funcionan como python (a diferencia del primer tipo del que estábamos hablando anteriormente) es que algunos tipos de objetos se administran de una manera especial; los valores idénticos se almacenan en caché para que no ocupen memoria adicional y se puedan comparar fácilmente (si tienen la misma dirección, son iguales). Este proceso se llama interning ; Todos los literales de cadena de python están internados (además de algunos otros tipos), aunque las cadenas creadas dinámicamente pueden no serlo.
En su código exacto, el diálogo semántico sería:
# before anything, since ''cat'' is a literal constant, add it to the intern cache
>>> id(''cat'') # grab the constant ''cat'' from the intern cache and look up
# it''s address
5181152
>>> a = ''cat'' # grab the constant ''cat'' from the intern cache and
# make the variable "a" point to it''s location
>>> b = ''cat'' # do the same thing with the variable "b"
>>> id(a) # look up the object "a" currently points to,
# then look up that object''s address
5181152
>>> id(b) # look up the object "b" currently points to,
# then look up that object''s address
5181152
Python reutiliza literales de cadenas de forma bastante agresiva. Las reglas por las que lo hace dependen de la implementación, pero CPython usa dos de las que tengo conocimiento:
- Las cadenas que contienen solo caracteres válidos en identificadores de Python son internados, lo que significa que se almacenan en una tabla grande y se reutilizan donde sea que ocurran. Entonces, no importa dónde uses
"cat"
, siempre se refiere al mismo objeto de cadena. - Los literales de cadena en el mismo bloque de código se reutilizan independientemente de su contenido y longitud. Si coloca una cadena literal de toda la Dirección de Gettysburg en una función, dos veces, es el mismo objeto de cadena en ambas ocasiones. En funciones separadas, son objetos diferentes:
def foo(): return "pack my box with five dozen liquor jugs" def bar(): return "pack my box with five dozen liquor jugs" assert foo() is bar() # AssertionError
Ambas optimizaciones se realizan en tiempo de compilación (es decir, cuando se genera el bytecode).
Por otro lado, algo como chr(99) + chr(97) + chr(116)
es una expresión de cadena que evalúa la cadena "cat"
. En un lenguaje dinámico como Python, su valor no puede conocerse en tiempo de compilación ( chr()
es una función incorporada, pero puede haberlo reasignado) por lo que normalmente no se interna. Por lo tanto, su id()
es diferente de la de "cat"
. Sin embargo, puede forzar una cadena para ser internado mediante la función intern()
. Así:
id(intern(chr(99) + chr(97) + chr(116))) == id("cat") # True
Como otros han mencionado, la internación es posible porque las cadenas son inmutables. No es posible cambiar "cat"
por "dog"
, en otras palabras. Debe generar un nuevo objeto de cadena, lo que significa que no hay peligro de que otros nombres que apunten a la misma cadena se vean afectados.
Solo como un lado, Python también convierte expresiones que contienen solo constantes (como "c" + "a" + "t"
) en constantes en tiempo de compilación, como se muestra a continuación. Estos se optimizarán para apuntar a objetos de cadena idénticos según las reglas anteriores.
>>> def foo(): "c" + "a" + "t"
...
>>> from dis import dis; dis(foo)
1 0 LOAD_CONST 5 (''cat'')
3 POP_TOP
4 LOAD_CONST 0 (None)
7 RETURN_VALUE
Todos los valores deben residir en algún lugar de la memoria. Es por eso que id(''cat'')
produce un valor. Usted lo llama una cadena "inexistente", pero claramente existe, simplemente no ha sido asignado a un nombre todavía.
Las cadenas son inmutables, por lo que el intérprete puede hacer cosas inteligentes como hacer que todas las instancias del literal ''cat''
sean el mismo objeto, de modo que id(a)
e id(b)
sean las mismas.
Operar en cadenas producirá nuevas cadenas. Estas pueden ser o no las mismas cadenas que las cadenas anteriores con el mismo contenido.
Tenga en cuenta que todos estos detalles son detalles de implementaciones de CPython, y pueden cambiar en cualquier momento. No necesita preocuparse por estos problemas en los programas reales.
''cat''
tiene una dirección porque la creas para pasarla a id()
. Aún no lo ha vinculado a un nombre, pero el objeto aún existe.
Python almacena en caché y reutiliza cadenas cortas. Pero si ensambla cadenas por concatenación, entonces se omite el código que busca en la memoria caché e intenta volver a usar.
Tenga en cuenta que el funcionamiento interno de la caché de cadenas es puro detalle de implementación y no se debe confiar en él.