Python Multiple Assignment Statements In One Line
All credit goes to @MarkDickinson, who answered this in a comment:
Notice the
+
in(target_list "=")+
, which means one or more copies. Infoo = bar = 5
, there are two(target_list "=")
productions, and theexpression_list
part is just5
All target_list
productions (i.e. things that look like foo =
) in an assignment statement get assigned, from left to right, to the expression_list
on the right end of the statement, after the expression_list
gets evaluated.
And of course the usual 'tuple-unpacking' assignment syntax works within this syntax, letting you do things like
>>> 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
Mark Dickinson explained the syntax of what is happening, but the weird examples involving foo
show that the semantics can be counter-intuitive.
In C, =
is a right-associative operator which returns as a value the RHS of the assignment so when you write x = y = 5
, y=5
is first evaluated (assigning 5 to y
in the process) and this value (5) is then assigned to x
.
Before I read this question, I naively assumed that roughly the same thing happens in Python. But, in Python =
isn't an expression (for example, 2 + (x = 5)
is a syntax error). So Python must achieve multiple assignments in another way.
We can disassemble rather than guess:
>>> 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
See this for a description of the byte code instructions.
The first instruction pushes 5 onto the stack.
The second instruction duplicates it -- so now the top of the stack has two 5s
STORE_NAME(name)
"Implements name = TOS" according to the byte code documentation
Thus STORE_Name(x)
implements x = 5
(the 5 on top of the stack), popping that 5 off the stack as it goes, after which STORE_Name(y)
implements y = 5
with the other 5 on the stack.
The rest of the bytecode isn't directly relevant here.
In the case of foo = foo[0] = [0]
the byte-code is more complicated because of the lists but has a fundamentally similar structure. The key observation is that once the list [0]
is created and placed on the stack then the instruction DUP_TOP
doesn't place another copy of [0]
on the stack, instead it places another reference to the list. In other words, at that stage the top two elements of the stack are aliases for the same list. This can be seen most clearly in the somewhat simpler case:
>>> x = y = [0]
>>> x[0] = 5
>>> y[0]
5
When foo = foo[0] = [0]
is executed, the list [0]
is first assigned to foo
and then an alias of the same list is assigned to foo[0]
. This is why it results in foo
being a circular reference.