¿Qué sucede entre bastidores cuando python agrega pequeños ints? [duplicar]


Esta pregunta ya tiene una respuesta aquí:

Estaba jugueteando con id recientemente y me di cuenta de que (c?) Python hace algo bastante sensato: asegura que los pequeños ints siempre tengan el mismo id.

>>> a, b, c, d, e = 1, 2, 3, 4, 5
>>> f, g, h, i, j = 1, 2, 3, 4, 5
>>> [id(x) == id(y) for x, y in zip([a, b, c, d, e], [f, g, h, i, j])]
[True, True, True, True, True]

Pero entonces se me ocurrió preguntarme si el mismo es cierto para los resultados de las operaciones matemáticas. Resulta que es:

>>> nines = [(x + y, 9) for x, y in enumerate(reversed(range(10)))]
>>> [id(x) == id(y) for x, y in nines]
[True, True, True, True, True, True, True, True, True, True]

Parece que empieza a fallar en n=257...

>>> a, b = 200 + 56, 256
>>> id(a) == id(b)
True
>>> a, b = 200 + 57, 257
>>> id(a) == id(b)
False

Pero a veces todavía funciona incluso con números más grandes:

>>> [id(2 * x + y) == id(300 + x) for x, y in enumerate(reversed(range(301)))][:10]
[True, True, True, True, True, True, True, True, True, True]

¿Qué está pasando aquí? ¿Cómo hace python esto?

Author: Robert Harvey, 2011-05-23

3 answers

Python mantiene un conjunto de int objetos en ciertos números. Cuando creas uno en ese rango, realmente obtienes una referencia al preexistente. Sospecho que esto es por razones de optimización.

Para los números fuera del rango de ese grupo, parece que obtiene un nuevo objeto cada vez que intenta crear uno.

$ python
Python 3.2 (r32:88445, Apr 15 2011, 11:09:05) 
[GCC 4.5.2 20110127 (prerelease)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> x = 300
>>> id(x)
140570345270544
>>> id(100+200)
140570372179568
>>> id(x*2)
140570345270512
>>> id(600)
140570345270576

Fuente

PyObject * PyInt_FromLong (long ival) Valor devuelto: Nueva referencia. Crear un nuevo objeto entero con un valor de ival.

La implementación actual mantiene una matriz de objetos enteros para todos enteros entre -5 y 256, cuando crear un int en ese rango que en realidad, sólo obtener una referencia a el objeto existente. Así debe ser posible cambiar el valor de 1. Me sospechar el comportamiento de Python en este caso no está definido. :-)

énfasis mío

 17
Author: Daenyth,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-05-23 19:02:05

Has caído en una trampa no infrecuente:

id(2 * x + y) == id(300 + x)

Las dos expresiones 2 * x + y y 300 + x no tienen vidas superpuestas. Eso significa que Python puede calcular el lado izquierdo, tomar su id y luego liberar el entero antes de calcular el lado derecho. Cuando CPython libera un entero lo pone en una lista de enteros liberados y luego lo reutiliza para un entero diferente la próxima vez que lo necesite. Así que sus ids coinciden incluso cuando el resultado de los cálculos son muy diferente:

>>> x, y = 100, 40000
>>> id(2 * x + y) == id(300 + x)
True
>>> 2 * x + y, 300 + x
(40200, 400)
 18
Author: Duncan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-05-23 19:03:59

AFAIK, id no tiene nada que ver con el tamaño del parámetro. DEBE devolver un identificador único de por vida, y PUEDE devolver el mismo resultado para dos parámetros diferentes, si no existen simultáneamente.

 2
Author: Hyperboreus,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-05-23 18:40:51