Python3 asignación múltiple y dirección de memoria

Después de leer esto y esto , que son bastante similares a mi pregunta, todavía no puedo entender el siguiente comportamiento:

a = 257 b = 257 print(a is b) #False a, b = 257, 257 print(a is b) #True 

Al imprimir id(a) e id(b) puedo ver que las variables, a las que se asignaron los valores en líneas separadas, tienen identificaciones diferentes, mientras que con la asignación múltiple, ambos valores tienen la misma identificación:

 a = 257 b = 257 print(id(a)) #139828809414512 print(id(b)) #139828809414224 a, b = 257, 257 print(id(a)) #139828809414416 print(id(b)) #139828809414416 

Pero es imposible explicar este comportamiento diciendo que la asignación múltiple de los mismos valores siempre crea punteros a la misma ID ya que:

 a, b = -1000, -1000 print(id(a)) #139828809414448 print(id(b)) #139828809414288 

¿Hay una regla clara que explique cuándo las variables obtienen el mismo id y cuándo no?

editar

información relevante: el código en esta pregunta se ejecutó en modo interactivo (ipython3)

Esto se debe a una constante optimización de plegado en el comstackdor de bytecode. Cuando el comstackdor de bytecode comstack un lote de sentencias, utiliza un dict para realizar un seguimiento de las constantes que se ven. Este dict fusiona automáticamente cualquier constante equivalente.

Aquí está la rutina responsable de registrar y numerar las constantes (así como algunas responsabilidades relacionadas):

 static int compiler_add_o(struct compiler *c, PyObject *dict, PyObject *o) { PyObject *t, *v; Py_ssize_t arg; t = _PyCode_ConstantKey(o); if (t == NULL) return -1; v = PyDict_GetItem(dict, t); if (!v) { arg = PyDict_Size(dict); v = PyInt_FromLong(arg); if (!v) { Py_DECREF(t); return -1; } if (PyDict_SetItem(dict, t, v) < 0) { Py_DECREF(t); Py_DECREF(v); return -1; } Py_DECREF(v); } else arg = PyInt_AsLong(v); Py_DECREF(t); return arg; } 

Puede ver que solo agrega una nueva entrada y asigna un nuevo número si no encuentra una constante equivalente ya presente. (El bit _PyCode_ConstantKey asegura que cosas como 0.0 , -0.0 y 0 sean consideradas desiguales).

En el modo interactivo, un lote termina cada vez que el intérprete tiene que ejecutar su comando, por lo que el plegado constante no ocurre en todos los comandos:

 >>> a = 1000 >>> b = 1000 >>> a is b False >>> a = 1000; b = 1000 # 1 batch >>> a is b True 

En un script, todas las declaraciones de nivel superior son un lote, por lo que ocurre un plegado más constante :

 a = 257 b = 257 print a is b 

En un guión, esto imprime True .

El código de una función hace que sus constantes se rastreen por separado del código que está fuera de la función, lo que limita el plegado constante:

 a = 257 def f(): b = 257 print a is b f() 

Incluso en un guión , esto imprime False .

Esto se debe a la optimización del intérprete de los pitones en el momento de UNPACK_SEQUENCE , durante la carga de los valores constantes. Cuando Python encuentra un iterable durante el desempaquetado, no carga los objetos duplicados varias veces, sino que solo mantiene el primer objeto y asigna todos sus nombres de variables duplicados a un puntero (en la implementación de CPython). Por lo tanto, todas sus variables se convertirán en las mismas referencias a un objeto. En el nivel de Python, puede pensar que este comportamiento usa un diccionario como el espacio de nombres que no guarda claves duplicadas.

En otras palabras, su desembalaje sería equivalente al siguiente comando:

 a = b = 257 

Y sobre los números negativos, en python 2.X no hay ninguna diferencia, pero en python 3.X parece que para números más pequeños que -5 python creará un nuevo objeto durante el desempaque:

 >>> a, b = -6, -6 >>> a is b False >>> a, b = -5, -5 >>> >>> a is b True 

Cualquier regla de este tipo es específica de la implementación. CPython, por ejemplo, preasigna objetos int para enteros pequeños (-5 a 256) como una optimización del rendimiento.

La única regla general es asumir que cualquier uso de un literal generará un nuevo objeto.