Multiple Assignment a = b = c
Steven D'Aprano
steve at pearwood.info
Tue Feb 16 10:16:23 EST 2016
On Tue, 16 Feb 2016 11:46 pm, srinivas devaki wrote:
> Hi,
>
> a = b = c
>
> as an assignment doesn't return anything, i ruled out a = b = c as
> chained assignment, like a = (b = c)
> SO i thought, a = b = c is resolved as
> a, b = [c, c]
That is one way of thinking of it. A better way would be:
a = c
b = c
except that isn't necessarily correct for complex assignments involving
attribute access or item assignment. A better way is:
_temp = c
a = _temp
b = _temp
del _temp
except the name "_temp" isn't actually used.
> at-least i fixed in my mind that every assignment like operation in
> python is done with references and then the references are binded to
> the named variables.
> like globals()['a'] = result()
That's broadly correct.
> but today i learned that this is not the case with great pain(7 hours
> of debugging.)
>
> class Mytest(object):
> def __init__(self, a):
> self.a = a
> def __getitem__(self, k):
> print('__getitem__', k)
> return self.a[k]
> def __setitem__(self, k, v):
> print('__setitem__', k, v)
> self.a[k] = v
>
> roots = Mytest([0, 1, 2, 3, 4, 5, 6, 7, 8])
> a = 4
> roots[4] = 6
> a = roots[a] = roots[roots[a]]
`roots[4] = 6` will give "__setitem__ 4 6", as you expect.
On the right hand side, you have:
roots[roots[a]]
which evaluates `roots[a]` first, giving "__getitem__ 4". That returns 6, as
you expect. So now you have `roots[6]`, which gives "__getitem__ 6", as you
expect, and returns 6.
The left hand side has:
a = roots[a] = ...
which becomes:
a = roots[a] = 6
which behaves like:
a = 6
roots[a] = 6
So you end up with:
a = roots[6] = 6
which gives "__setitem__ 6 6", **not** "__setitem__ 4 6" like you expected.
Here is a simpler demonstration:
py> L = [0, 1, 2, 3, 4, 5, 6]
py> a = L[a//100] = 500
py> print a
500
py> print L
[0, 1, 2, 3, 4, 500, 6]
Let's look at the byte-code generated by the statement:
a = L[a] = x
The exact byte-code used will depend on the version of Python you have, but
for 2.7 it looks like this:
py> from dis import dis
py> code = compile("a = L[a] = x", "", "exec")
py> dis(code)
1 0 LOAD_NAME 0 (x)
3 DUP_TOP
4 STORE_NAME 1 (a)
7 LOAD_NAME 2 (L)
10 LOAD_NAME 1 (a)
13 STORE_SUBSCR
14 LOAD_CONST 0 (None)
17 RETURN_VALUE
Translated to English:
- evaluate the expression `x` and push the result onto the stack;
- duplicate the top value on the stack;
- pop the top value off the stack and assign to name `a`;
- evaluate the name `L`, and push the result onto the stack;
- evaluate the name `a`, and push the result onto the stack;
- call setattr with the top three items from the stack.
> SO isn't it counter intuitive from all other python operations.
> like how we teach on how python performs a swap operation???
No. Let's look at the equivalent swap:
py> L = [10, 20, 30, 40, 50]
py> a = 3
py> a, L[a] = L[a], a
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
IndexError: list assignment index out of range
This is equivalent to:
_temp1 = L[a] # 40 pushed onto the stack
_temp2 = a # 3 pushed onto the stack
a = _temp1 # 40 # rotate the stack, and pull the top item 40
L[a] = _temp2 # L[40] = 3
which obviously fails. Here's the byte-code:
py> code = compile("a, L[a] = L[a], a", "", "exec")
py> dis(code)
1 0 LOAD_NAME 0 (L)
3 LOAD_NAME 1 (a)
6 BINARY_SUBSCR
7 LOAD_NAME 1 (a)
10 ROT_TWO
11 STORE_NAME 1 (a)
14 LOAD_NAME 0 (L)
17 LOAD_NAME 1 (a)
20 STORE_SUBSCR
21 LOAD_CONST 0 (None)
24 RETURN_VALUE
If you do the swap in the other order, it works:
py> L = [10, 20, 30, 40, 50]
py> a = 3
py> L[a], a = a, L[a]
py> print a
40
py> print L
[10, 20, 30, 3, 50]
In all cases, the same rule applies:
- evaluate the right hand side from left-most to right-most, pushing the
values onto the stack;
- perform assignments on the left hand side, from left-most to right-most.
--
Steven
More information about the Python-list
mailing list