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