Llego tarde pero, ¿quieres alguna fuente con tu respuesta? Intentaré redactar esto de manera introductoria para que más personas puedan seguirlo.
Lo bueno de CPython es que realmente puedes ver la fuente de esto. Voy a usar enlaces para la versión 3.5 , pero encontrar los correspondientes 2.x es trivial.
En CPython, la función C-API que maneja la creación de un nuevo int
objeto es PyLong_FromLong(long v)
. La descripción de esta función es:
La implementación actual mantiene una matriz de objetos enteros para todos los enteros entre -5 y 256, cuando crea un int en ese rango, en realidad solo obtiene una referencia al objeto existente . Por lo tanto, debería ser posible cambiar el valor de 1. Sospecho que el comportamiento de Python en este caso no está definido. :-)
(Mi cursiva)
No sé sobre ti, pero veo esto y pienso: ¡encontremos esa matriz!
Si no ha jugado con el código C que implementa CPython , debería hacerlo ; todo es bastante organizado y legible. Para nuestro caso, necesitamos mirar en elObjects
subdirectorio del árbol de directorios del código fuente principal .
PyLong_FromLong
trata con long
objetos, por lo que no debería ser difícil deducir que necesitamos echar un vistazo dentro longobject.c
. Después de mirar dentro, puede pensar que las cosas son caóticas; son, pero no temas, la función que estamos buscando es escalofriante línea 230 esperando que la revisemos. Es una función pequeña, por lo que el cuerpo principal (excluyendo las declaraciones) se pega fácilmente aquí:
PyObject *
PyLong_FromLong(long ival)
{
// omitting declarations
CHECK_SMALL_INT(ival);
if (ival < 0) {
/* negate: cant write this as abs_ival = -ival since that
invokes undefined behaviour when ival is LONG_MIN */
abs_ival = 0U-(unsigned long)ival;
sign = -1;
}
else {
abs_ival = (unsigned long)ival;
}
/* Fast path for single-digit ints */
if (!(abs_ival >> PyLong_SHIFT)) {
v = _PyLong_New(1);
if (v) {
Py_SIZE(v) = sign;
v->ob_digit[0] = Py_SAFE_DOWNCAST(
abs_ival, unsigned long, digit);
}
return (PyObject*)v;
}
Ahora, no somos C master-code-haxxorz pero tampoco somos tontos, podemos ver que CHECK_SMALL_INT(ival);
nos mira a todos seductoramente; Podemos entender que tiene algo que ver con esto.Vamos a ver:
#define CHECK_SMALL_INT(ival) \
do if (-NSMALLNEGINTS <= ival && ival < NSMALLPOSINTS) { \
return get_small_int((sdigit)ival); \
} while(0)
Entonces, es una macro que llama a la función get_small_int
si el valorival
cumple la condición:
if (-NSMALLNEGINTS <= ival && ival < NSMALLPOSINTS)
Entonces, ¿qué son NSMALLNEGINTS
y NSMALLPOSINTS
? Macros! Aquí están :
#ifndef NSMALLPOSINTS
#define NSMALLPOSINTS 257
#endif
#ifndef NSMALLNEGINTS
#define NSMALLNEGINTS 5
#endif
Entonces nuestra condición es if (-5 <= ival && ival < 257)
llamadaget_small_int
.
A continuación, veamos get_small_int
en todo su esplendor (bueno, solo veremos su cuerpo porque ahí es donde están las cosas interesantes):
PyObject *v;
assert(-NSMALLNEGINTS <= ival && ival < NSMALLPOSINTS);
v = (PyObject *)&small_ints[ival + NSMALLNEGINTS];
Py_INCREF(v);
De acuerdo, declarar un PyObject
, afirme que la condición anterior se cumple y ejecute la asignación:
v = (PyObject *)&small_ints[ival + NSMALLNEGINTS];
small_ints
se parece mucho a esa matriz que hemos estado buscando, ¡y lo es! ¡Podríamos haber leído la maldita documentación y lo habríamos sabido todo el tiempo!:
/* Small integers are preallocated in this array so that they
can be shared.
The integers that are preallocated are those in the range
-NSMALLNEGINTS (inclusive) to NSMALLPOSINTS (not inclusive).
*/
static PyLongObject small_ints[NSMALLNEGINTS + NSMALLPOSINTS];
Así que sí, este es nuestro chico. Cuando quieras crear un nuevoint
en el rango[NSMALLNEGINTS, NSMALLPOSINTS)
, simplemente obtendrá una referencia a un objeto ya existente que ha sido previamente asignado.
Como la referencia se refiere al mismo objeto, la emisión id()
directamente o verificar la identidad conis
él devolverá exactamente lo mismo.
Pero, ¿cuándo se asignan?
Durante la inicialización en_PyLong_Init
Python con mucho gusto entrará en un bucle for, haga esto por usted:
for (ival = -NSMALLNEGINTS; ival < NSMALLPOSINTS; ival++, v++) {
¡Mira la fuente para leer el cuerpo del bucle!
Espero que mi explicación te haya aclarado las cosas ahora (juego de palabras obviamente intencionado).
Pero, 257 is 257
? ¿Qué pasa?
En realidad, esto es más fácil de explicar, y ya he intentado hacerlo ; se debe al hecho de que Python ejecutará esta declaración interactiva como un solo bloque:
>>> 257 is 257
Durante la compilación de esta declaración, CPython verá que tiene dos literales coincidentes y usará la misma PyLongObject
representación 257
. Puede ver esto si hace la compilación usted mismo y examina su contenido:
>>> codeObj = compile("257 is 257", "blah!", "exec")
>>> codeObj.co_consts
(257, None)
Cuando CPython realiza la operación, ahora solo va a cargar exactamente el mismo objeto:
>>> import dis
>>> dis.dis(codeObj)
1 0 LOAD_CONST 0 (257) # dis
3 LOAD_CONST 0 (257) # dis again
6 COMPARE_OP 8 (is)
Entonces is
volveremos True
.