Una pregunta sobre la singularidad de la instancia de cadena en Python

Estaba tratando de averiguar qué enteros Python solo ejemplifica una vez (-6 a 256 que parece), y en el proceso tropezé con algún comportamiento de cadena. No puedo ver el patrón. A veces, cadenas iguales creadas de diferentes maneras comparten lo mismo Identificación, a veces no. Este código:

A = "10000" B = "10000" C = "100" + "00" D = "%i"%10000 E = str(10000) F = str(10000) G = str(100) + "00" H = "0".join(("10","00")) for obj in (A,B,C,D,E,F,G,H): print obj, id(obj), obj is A 

huellas dactilares:

 10000 4959776 Verdadero
 10000 4959776 Verdadero
 10000 4959776 Verdadero
 10000 4959776 Verdadero
 10000 4959456 Falso
 10000 4959488 falso
 10000 4959520 Falso
 10000 4959680 Falso

Ni siquiera veo el patrón, excepto por el hecho de que los primeros cuatro no tienen una llamada de función explícita, pero seguramente no puede ser así, ya que el ” + ” en C, por ejemplo, implica una llamada de función para agregar . Especialmente no entiendo por qué C y G son diferentes, ya que eso implica que los identificadores de los componentes de la adición son más importantes que el resultado.

Entonces, ¿cuál es el tratamiento especial que sufre AD, haciéndolos salir como la misma instancia?

En términos de especificación de lenguaje, cualquier comstackdor y tiempo de ejecución Python compatible está totalmente permitido, para cualquier instancia de un tipo inmutable, para crear una nueva instancia O para encontrar una instancia existente del mismo tipo que sea igual al valor requerido y usar una nueva referencia para Esa misma instancia. Esto significa que siempre es incorrecto usar la comparación de id. O id. Entre inmutables, y cualquier versión menor puede modificar o cambiar la estrategia en este asunto para mejorar la optimización.

En términos de implementaciones, la compensación es bastante clara: tratar de reutilizar una instancia existente puede significar tiempo invertido (quizás desperdiciado) tratando de encontrar tal instancia, pero si el bash tiene éxito, entonces se guarda algo de memoria (así como el tiempo para asignar y luego libere los bits de memoria necesarios para mantener una nueva instancia).

La forma de resolver esas concesiones de implementación no es del todo obvia: si puede identificar heurísticas que indiquen que es probable que se encuentre una instancia existente adecuada y que la búsqueda (incluso si falla) sea rápida, es posible que desee intentar la búsqueda y -reuse cuando las heurísticas lo sugieran, pero omítelo de otra manera.

En sus observaciones, parece haber encontrado una implementación particular de liberación de puntos que realiza un mínimo de optimización de mirilla cuando es completamente segura, rápida y simple, por lo que las asignaciones A a D se reducen exactamente a lo mismo que A (pero E a F no, ya que implican funciones o métodos con nombre que los autores del optimizador pueden haber considerado razonablemente no 100% seguros para asumir la semántica de –y un ROI bajo si se hizo eso- por lo que no están optimizados para la mirilla ”.

Por lo tanto, la reutilización de A a D en la misma instancia se reduce a que A y B lo hacen (ya que C y D se optimizan para la mirilla a exactamente la misma construcción).

Esa reutilización, a su vez, sugiere claramente tácticas de comstackción / heurísticas del optimizador mediante las cuales constantes literales idénticas de un tipo inmutable en el mismo espacio de nombres local de la función se .func_code.co_consts para hacer referencia a una sola instancia en la función .func_code.co_consts (para usar la terminología de CPython actual para los atributos de funciones y objetos de código): tácticas y heurísticas razonables, ya que la reutilización del mismo literal constante inmutable dentro de una función es algo frecuente, Y el precio solo se paga una vez (en tiempo de comstackción) mientras que la ventaja se acumula muchas veces (cada vez la función se ejecuta, tal vez dentro de bucles, etc, etc).

(Sucede que estas tácticas y heurísticas específicas, dados sus compromisos claramente positivos, se han generalizado en todas las versiones recientes de CPython y, creo, de IronPython, Jython y PyPy también ;-).

Este es un estudio que vale la pena e interesante si planea escribir comstackdores, entornos de tiempo de ejecución, optimizadores de mirillas, etc., para Python o para lenguajes similares. Supongo que un estudio profundo de los aspectos internos (idealmente de muchas implementaciones correctas diferentes, por supuesto, para no fijarse en las peculiaridades de una específica; lo bueno de Python actualmente disfruta de al menos 4 implementaciones independientes dignas de producción, por no mencionar ¡varias versiones de cada uno!) también pueden ayudar, indirectamente, a hacer que uno sea un mejor progtwigdor de Python, pero es particularmente importante concentrarse en lo que está garantizado por el propio lenguaje, que es algo menos de lo que encontrará en común entre las implementaciones separadas, porque las partes que “simplemente suceden” están en común en este momento (sin que así lo exijan las especificaciones del idioma) pueden cambiar perfectamente debajo de usted en el próximo punto de lanzamiento de una u otra implementación y, si su código de producción fue erróneamente confiar en tales detalles, que podrían causar sorpresas desagradables ;-). Además, casi nunca es necesario, o incluso particularmente útil, confiar en tales detalles de implementación variables en lugar de en el comportamiento exigido por el lenguaje (a menos que esté codificando algo como un optimizador, depurador, generador de perfiles, etc.) ).

Python tiene permitido alinear constantes de cadena; A, B, C, D son en realidad los mismos literales (si Python ve una expresión constante, la trata como una constante).

str es en realidad una clase, por lo que str(whatever) está llamando al constructor de esta clase, que debería producir un objeto nuevo. Esto explica E, F, G (tenga en cuenta que cada uno de estos tiene una identidad separada).

En cuanto a H, no estoy seguro, pero buscaría una explicación de que esta expresión es demasiado complicada para que Python descubra que en realidad es una constante, por lo que calcula una nueva cadena.

Creo que las cadenas cortas que se pueden evaluar en el momento de la comstackción, se internarán automáticamente. En los últimos ejemplos, el resultado no se puede evaluar en el momento de la comstackción porque se puede redefinir str o join .

En respuesta a la sugerencia de S.Lott de examinar el código de byte:

 import dis def moo(): A = "10000" B = "10000" C = "100" + "00" D = "%i"%10000 E = str(10000) F = str(10000) G = "1000"+str(0) H = "0".join(("10","00")) I = str("10000") for obj in (A,B,C,D,E,F,G,H, I): print obj, id(obj), obj is A moo() print dis.dis(moo) 

rendimientos

 10000 4968128 True 10000 4968128 True 10000 4968128 True 10000 4968128 True 10000 2840928 False 10000 2840896 False 10000 2840864 False 10000 2840832 False 10000 4968128 True 4 0 LOAD_CONST 1 ('10000') 3 STORE_FAST 0 (A) 5 6 LOAD_CONST 1 ('10000') 9 STORE_FAST 1 (B) 6 12 LOAD_CONST 10 ('10000') 15 STORE_FAST 2 (C) 7 18 LOAD_CONST 11 ('10000') 21 STORE_FAST 3 (D) 8 24 LOAD_GLOBAL 0 (str) 27 LOAD_CONST 5 (10000) 30 CALL_FUNCTION 1 33 STORE_FAST 4 (E) 9 36 LOAD_GLOBAL 0 (str) 39 LOAD_CONST 5 (10000) 42 CALL_FUNCTION 1 45 STORE_FAST 5 (F) 10 48 LOAD_CONST 6 ('1000') 51 LOAD_GLOBAL 0 (str) 54 LOAD_CONST 7 (0) 57 CALL_FUNCTION 1 60 BINARY_ADD 61 STORE_FAST 6 (G) 11 64 LOAD_CONST 8 ('0') 67 LOAD_ATTR 1 (join) 70 LOAD_CONST 12 (('10', '00')) 73 CALL_FUNCTION 1 76 STORE_FAST 7 (H) 12 79 LOAD_GLOBAL 0 (str) 82 LOAD_CONST 1 ('10000') 85 CALL_FUNCTION 1 88 STORE_FAST 8 (I) 14 91 SETUP_LOOP 66 (to 160) 94 LOAD_FAST 0 (A) 97 LOAD_FAST 1 (B) 100 LOAD_FAST 2 (C) 103 LOAD_FAST 3 (D) 106 LOAD_FAST 4 (E) 109 LOAD_FAST 5 (F) 112 LOAD_FAST 6 (G) 115 LOAD_FAST 7 (H) 118 LOAD_FAST 8 (I) 121 BUILD_TUPLE 9 124 GET_ITER >> 125 FOR_ITER 31 (to 159) 128 STORE_FAST 9 (obj) 15 131 LOAD_FAST 9 (obj) 134 PRINT_ITEM 135 LOAD_GLOBAL 2 (id) 138 LOAD_FAST 9 (obj) 141 CALL_FUNCTION 1 144 PRINT_ITEM 145 LOAD_FAST 9 (obj) 148 LOAD_FAST 0 (A) 151 COMPARE_OP 8 (is) 154 PRINT_ITEM 155 PRINT_NEWLINE 156 JUMP_ABSOLUTE 125 >> 159 POP_BLOCK >> 160 LOAD_CONST 0 (None) 163 RETURN_VALUE 

por lo que parece que, de hecho, el comstackdor entiende que AD significa lo mismo, y por lo tanto ahorra memoria al generarla solo una vez (como lo sugieren Alex, Maciej y Greg). (caso agregado I parece que solo I str () dándome cuenta de que está tratando de hacer una cadena a partir de una cadena, y simplemente de pasarla).

Gracias a todos, eso es mucho más claro ahora.