Python almacena en caché los enteros en el rango [-5, 256]
, por lo que se espera que los enteros en ese rango también sean idénticos.
Lo que ves es el compilador de Python que optimiza literales idénticos cuando forman parte del mismo texto.
Al escribir en el shell de Python, cada línea es una declaración completamente diferente, analizada en un momento diferente, así:
>>> a = 257
>>> b = 257
>>> a is b
False
Pero si pones el mismo código en un archivo:
$ echo 'a = 257
> b = 257
> print a is b' > testing.py
$ python testing.py
True
Esto sucede siempre que el analizador tiene la oportunidad de analizar dónde se utilizan los literales, por ejemplo, al definir una función en el intérprete interactivo:
>>> def test():
... a = 257
... b = 257
... print a is b
...
>>> dis.dis(test)
2 0 LOAD_CONST 1 (257)
3 STORE_FAST 0 (a)
3 6 LOAD_CONST 1 (257)
9 STORE_FAST 1 (b)
4 12 LOAD_FAST 0 (a)
15 LOAD_FAST 1 (b)
18 COMPARE_OP 8 (is)
21 PRINT_ITEM
22 PRINT_NEWLINE
23 LOAD_CONST 0 (None)
26 RETURN_VALUE
>>> test()
True
>>> test.func_code.co_consts
(None, 257)
Tenga en cuenta cómo el código compilado contiene una única constante para 257
.
En conclusión, el compilador de código de bytes de Python no puede realizar optimizaciones masivas (como lenguajes de tipos estáticos), pero hace más de lo que cree. Una de estas cosas es analizar el uso de literales y evitar duplicarlos.
Tenga en cuenta que esto no tiene que ver con la caché, porque también funciona para flotantes, que no tienen caché:
>>> a = 5.0
>>> b = 5.0
>>> a is b
False
>>> a = 5.0; b = 5.0
>>> a is b
True
Para literales más complejos, como tuplas, "no funciona":
>>> a = (1,2)
>>> b = (1,2)
>>> a is b
False
>>> a = (1,2); b = (1,2)
>>> a is b
False
Pero los literales dentro de la tupla se comparten:
>>> a = (257, 258)
>>> b = (257, 258)
>>> a[0] is b[0]
False
>>> a[1] is b[1]
False
>>> a = (257, 258); b = (257, 258)
>>> a[0] is b[0]
True
>>> a[1] is b[1]
True
Con respecto a por qué ves que PyInt_Object
se crean dos , supongo que esto se hace para evitar una comparación literal. por ejemplo, el número 257
puede expresarse mediante varios literales:
>>> 257
257
>>> 0x101
257
>>> 0b100000001
257
>>> 0o401
257
El analizador tiene dos opciones:
- Convierta los literales a una base común antes de crear el número entero y vea si los literales son equivalentes. luego crea un único objeto entero.
- Cree los objetos enteros y vea si son iguales. En caso afirmativo, mantenga solo un valor único y asígnelo a todos los literales; de lo contrario, ya tiene los números enteros para asignar.
Probablemente el analizador de Python usa el segundo enfoque, que evita reescribir el código de conversión y también es más fácil de extender (por ejemplo, también funciona con flotantes).
Al leer el Python/ast.c
archivo, la función que analiza todos los números es parsenumber
, que llama PyOS_strtoul
para obtener el valor entero (para enteros) y finalmente llama PyLong_FromString
:
x = (long) PyOS_strtoul((char *)s, (char **)&end, 0);
if (x < 0 && errno == 0) {
return PyLong_FromString((char *)s,
(char **)0,
0);
}
Como puede ver aquí, el analizador no verifica si ya encontró un número entero con el valor dado, por lo que esto explica por qué ve que se crean dos objetos int, y esto también significa que mi conjetura fue correcta: el analizador primero crea las constantes y solo después optimiza el código de bytes para usar el mismo objeto para constantes iguales.
El código que realiza esta verificación debe estar en algún lugar de Python/compile.c
o Python/peephole.c
, ya que estos son los archivos que transforman el AST en código de bytes.
En particular, la compiler_add_o
función parece la que lo hace. Hay este comentario en compiler_lambda
:
/* Make None the first constant, so the lambda can't have a
docstring. */
if (compiler_add_o(c, c->u->u_consts, Py_None) < 0)
return 0;
Entonces parece que compiler_add_o
se usa para insertar constantes para funciones / lambdas, etc. La compiler_add_o
función almacena las constantes en un dict
objeto, y de esto se deduce inmediatamente que las constantes iguales caerán en la misma ranura, lo que resultará en una única constante en el bytecode final.