It's time to pull out dis .
>>> import dis >>> dis.dis(foo) 2 0 LOAD_GLOBAL 0 (Bar) 3 CALL_FUNCTION 0 6 DUP_TOP 7 STORE_FAST 0 (bar) 10 STORE_FAST 1 (initial_bar) 3 13 SETUP_LOOP 32 (to 48) >> 16 LOAD_GLOBAL 1 (True) 19 POP_JUMP_IF_FALSE 47 4 22 LOAD_GLOBAL 0 (Bar) 25 CALL_FUNCTION 0 28 STORE_FAST 2 (next_bar) 5 31 LOAD_FAST 2 (next_bar) 34 DUP_TOP 35 STORE_FAST 0 (bar) 38 LOAD_FAST 0 (bar) 41 STORE_ATTR 2 (next_bar) 44 JUMP_ABSOLUTE 16 >> 47 POP_BLOCK 6 >> 48 LOAD_FAST 1 (initial_bar) 51 RETURN_VALUE
If you look closely at this, you will see that in the critical line (line 5 see numbers on the left, positions 31-47), it does this:
- Download
next_bar (31) twice (34); - Write it (the first instance on the stack) to
bar (35); - Write it (second copy
bar.next_bar stack) in bar.next_bar (38, 41).
This is more obvious in a minimal test case.
>>> def a(): ... b = c = d ... >>> dis.dis(a) 2 0 LOAD_GLOBAL 0 (d) 3 DUP_TOP 4 STORE_FAST 0 (b) 7 STORE_FAST 1 (c) 10 LOAD_CONST 0 (None) 13 RETURN_VALUE
See what he does. This means that b = c = d actually equivalent to b = d; c = d b = d; c = d . Usually this does not matter, but in the case mentioned originally, it matters. This means that in the critical line
bar = bar.next_bar = next_bar
not equivalent
bar.next_bar = next_bar bar = next_bar
Instead
bar = next_bar bar.next_bar = next_bar
This is actually documented in section 6.2 of the Python documentation, Simple Instructions, Assignment statements :
The assignment operator evaluates the list of expressions (remember that this can be a single expression or a list separated by commas, the last of which has a tuple) and assigns a single result object to each of the target lists, from left to right .
There is also a warning in this section that applies to this case:
A WARNING. Although the definition of assignment means that overlaps between the left and right sides are “safe (for example, a, b = b, a sweeps two variables), overlaps in the collection of assigned variables are not safe! For example, the following program prints [0, 2] :
x = [0, 1] i = 0 i, x[i] = 1, 2 print x
You can go to bar.next_bar = bar = next_bar , and it really will bring the desired result initially, but pity someone (including the author of the original after a while!) Who will have to read the code later and enjoy that fact, in words that, I I'm sure Tim would use it if he thought about them,
Explicit is better than a potentially tangled corner case.