lista - Sentencias de asignación múltiple de Python en una línea
lista de funciones de python (4)
Mark Dickinson explicó la sintaxis de lo que está sucediendo, pero los ejemplos extraños que involucran a foo
muestran que la semántica puede ser contraintuitiva.
En C, =
es un operador asociativo a la derecha que devuelve como un valor la RHS de la asignación, de modo que cuando escribe x = y = 5
, y=5
se evalúa primero (asignando 5 a y
en el proceso) y este valor (5 ) entonces se asigna a x
.
Antes de leer esta pregunta, asumí ingenuamente que en Python ocurre lo mismo. Pero, en Python =
no es una expresión (por ejemplo, 2 + (x = 5)
es un error de sintaxis). Entonces Python debe lograr múltiples tareas de otra manera.
Podemos desmontar en lugar de adivinar:
>>> import dis
>>> dis.dis(''x = y = 5'')
1 0 LOAD_CONST 0 (5)
3 DUP_TOP
4 STORE_NAME 0 (x)
7 STORE_NAME 1 (y)
10 LOAD_CONST 1 (None)
13 RETURN_VALUE
Vea this para una descripción de las instrucciones del código de bytes.
La primera instrucción empuja 5 en la pila.
La segunda instrucción lo duplica, así que ahora la parte superior de la pila tiene dos 5s
STORE_NAME(name)
"Implements name = TOS" de acuerdo con la documentación del código de bytes
Así, STORE_Name(x)
implementa x = 5
(el 5 en la parte superior de la pila), sacando ese 5 de la pila a medida que avanza, después de lo cual STORE_Name(y)
implementa y = 5
con los otros 5 en la pila.
El resto del código de bytes no es directamente relevante aquí.
En el caso de foo = foo[0] = [0]
el código de bytes es más complicado debido a las listas pero tiene una estructura fundamentalmente similar. La observación clave es que una vez que la lista [0]
se crea y se coloca en la pila, la instrucción DUP_TOP
no coloca otra copia de [0]
en la pila, sino que coloca otra referencia a la lista. En otras palabras, en esa etapa, los dos elementos superiores de la pila son alias para la misma lista. Esto se puede ver más claramente en el caso algo más simple:
>>> x = y = [0]
>>> x[0] = 5
>>> y[0]
5
Cuando se ejecuta foo = foo[0] = [0]
, la lista [0]
se asigna primero a foo
y luego se asigna un alias de la misma lista a foo[0]
. Por eso resulta que foo
es una referencia circular.
(No se preocupe, esta no es otra pregunta sobre cómo desempacar las tuplas).
En python, una declaración como foo = bar = baz = 5
asigna las variables foo, bar y baz a 5. Asigna estas variables de izquierda a derecha, como lo demuestran los ejemplos más desagradables como
>>> foo[0] = foo = [0]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name ''foo'' is not defined
>>> foo = foo[0] = [0]
>>> foo
[[...]]
>>> foo[0]
[[...]]
>>> foo is foo[0]
True
Pero la referencia del lenguaje python establece que las declaraciones de asignación tienen la forma
(target_list "=")+ (expression_list | yield_expression)
y en la asignación, la expression_list
se evalúa primero y luego se realiza la asignación.
Entonces, ¿cómo puede la línea foo = bar = 5
ser válida, dado que bar = 5
no es una expression_list
? ¿Cómo se analizan y evalúan estas múltiples tareas en una línea? ¿Estoy leyendo mal la referencia de lenguaje?
Todo el crédito es para @MarkDickinson, quien respondió esto en un comentario:
Observe el
+
en(target_list "=")+
, que significa una o más copias. Enfoo = bar = 5
, hay dos(target_list "=")
, y la parteexpression_list
es solo5
Todas target_list
producciones de target_list
(es decir, las cosas que parecen foo =
) en una declaración de asignación se asignan, de izquierda a derecha, a la expression_list
en el extremo derecho de la declaración, después de expression_list
se evalúe la lista_expresión.
Y, por supuesto, la sintaxis de asignación habitual de ''desempaquetar tuplas'' funciona dentro de esta sintaxis, permitiéndole hacer cosas como
>>> foo, boo, moo = boo[0], moo[0], foo[0] = moo[0], foo[0], boo[0] = [0], [0], [0]
>>> foo
[[[[...]]]]
>>> foo[0] is boo
True
>>> foo[0][0] is moo
True
>>> foo[0][0][0] is foo
True
https://docs.python.org/3/reference/simple_stmts.html#grammar-token-assignment_stmt
Una declaración de asignación evalúa la lista de expresiones (recuerde que esto puede ser una expresión única o una lista separada por comas; esta última produce una tupla) y asigna el único objeto resultante a cada una de las listas de destino, de izquierda a derecha.
bar = 5
no es una expresión. La asignación múltiple es una declaración separada de la declaración de asignación; la expresión es todo a la derecha de la extrema derecha =
.
Una buena manera de pensarlo es que el más a la derecha =
es el separador principal; Todo a la derecha sucede de izquierda a derecha, y todo a la izquierda sucede de izquierda a derecha también.