functions - ¿Qué ocurre con este cambio de comportamiento de desempaquetado de Python2 a Python3?
python list functions (2)
Ayer me encontré con esta extraña diferencia de desempaquetado entre Python 2 y Python 3, y no encontré ninguna explicación después de una rápida búsqueda en Google.
Python 2.7.8
a = 257
b = 257
a is b # False
a, b = 257, 257
a is b # False
Python 3.4.2
a = 257
b = 257
a is b # False
a, b = 257, 257
a is b # True
Sé que probablemente no afecte la corrección de un programa, pero me molesta un poco. ¿Alguien podría dar algunas ideas sobre esta diferencia en el desempaque?
Creo que esto es en realidad por accidente, ya que no puedo reproducir el comportamiento con Python 3.2.
Existe este problema http://bugs.python.org/issue11244 que introduce un CONST_STACK
para solucionar problemas con tuplas constantes con números negativos que no se optimizan (consulte los parches contra peephole.c
, que contiene las ejecuciones del optimizador de Python).
Esto parece haber llevado también al comportamiento dado. Sigo investigando esto :)
Este comportamiento, al menos en parte, tiene que ver con la forma en que el intérprete realiza el plegado constante y la forma en que el REPL ejecuta el código.
Primero, recuerde que CPython primero compila el código (para AST y luego el bytecode). Luego evalúa el bytecode. Durante la compilación, el script busca objetos que son inmutables y los almacena en caché. También los deduplica. Así que si ve
a = 257
b = 257
almacenará ayb contra el mismo objeto:
import dis
def f():
a = 257
b = 257
dis.dis(f)
#>>> 4 0 LOAD_CONST 1 (257)
#>>> 3 STORE_FAST 0 (a)
#>>>
#>>> 5 6 LOAD_CONST 1 (257)
#>>> 9 STORE_FAST 1 (b)
#>>> 12 LOAD_CONST 0 (None)
#>>> 15 RETURN_VALUE
Tenga en cuenta el LOAD_CONST 1
. El 1
es el índice en co_consts
:
f.__code__.co_consts
#>>> (None, 257)
Así que estos dos cargan el mismo 257
. ¿Por qué no ocurre esto con:
$ python2
Python 2.7.8 (default, Sep 24 2014, 18:26:21)
>>> a = 257
>>> b = 257
>>> a is b
False
$ python3
Python 3.4.2 (default, Oct 8 2014, 13:44:52)
>>> a = 257
>>> b = 257
>>> a is b
False
?
Cada línea en este caso es una unidad de compilación separada y la deduplicación no puede pasar a través de ellas. Funciona de manera similar a
compile a = 257
run a = 257
compile b = 257
run b = 257
compile a is b
run a is b
Como tal, estos objetos de código tendrán cachés constantes únicos. Esto implica que si eliminamos el salto de línea, devuelve True
:
>>> a = 257; b = 257
>>> a is b
True
De hecho, este es el caso de ambas versiones de Python. De hecho, esto es exactamente por qué
>>> a, b = 257, 257
>>> a is b
True
devuelve True
también; no es por ningún atributo de desempaquetado; Simplemente se colocan en la misma unidad de compilación.
Esto devuelve False
para las versiones que no se pliegan correctamente; Filmor enlaza con Ideone, que muestra este fallo en 2.7.3 y 3.2.3. En estas versiones, las tuplas creadas no comparten sus elementos con las otras constantes:
import dis
def f():
a, b = 257, 257
print(a is b)
print(f.__code__.co_consts)
#>>> (None, 257, (257, 257))
n = f.__code__.co_consts[1]
n1 = f.__code__.co_consts[2][0]
n2 = f.__code__.co_consts[2][1]
print(id(n), id(n1), id(n2))
#>>> (148384292, 148384304, 148384496)
Una vez más, sin embargo, esto no se trata de un cambio en cómo se desempaquetan los objetos; es solo un cambio en la forma en que los objetos se almacenan en co_consts
.