Groups | Search | Server Info | Keyboard shortcuts | Login | Register [http] [https] [nntp] [nntps]


Groups > comp.lang.python > #103000 > unrolled thread

Multiple Assignment a = b = c

Started bysrinivas devaki <mr.eightnoteight@gmail.com>
First post2016-02-16 18:16 +0530
Last post2016-02-16 21:30 +0530
Articles 3 — 2 participants

Back to article view | Back to comp.lang.python


Contents

  Multiple Assignment a = b = c srinivas devaki <mr.eightnoteight@gmail.com> - 2016-02-16 18:16 +0530
    Re: Multiple Assignment a = b = c Steven D'Aprano <steve@pearwood.info> - 2016-02-17 02:16 +1100
      Re: Multiple Assignment a = b = c srinivas devaki <mr.eightnoteight@gmail.com> - 2016-02-16 21:30 +0530

#103000 — Multiple Assignment a = b = c

Fromsrinivas devaki <mr.eightnoteight@gmail.com>
Date2016-02-16 18:16 +0530
SubjectMultiple Assignment a = b = c
Message-ID<mailman.161.1455626848.22075.python-list@python.org>
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]


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()

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]]


the above program's output is
__setitem__ 4 6
__getitem__ 4
__getitem__ 6
__setitem__ 6 6


But the output that i expected is
__setitem__ 4 6
__getitem__ 4
__getitem__ 6
__setitem__ 4 6

SO isn't it counter intuitive from all other python operations.
like how we teach on how python performs a swap operation???

I just want to get a better idea around this.

-- 
Regards
Srinivas Devaki
Junior (3rd yr) student at Indian School of Mines,(IIT Dhanbad)
Computer Science and Engineering Department
ph: +91 9491 383 249
telegram_id: @eightnoteight

[toc] | [next] | [standalone]


#103012

FromSteven D'Aprano <steve@pearwood.info>
Date2016-02-17 02:16 +1100
Message-ID<56c33d48$0$1587$c3e8da3$5496439d@news.astraweb.com>
In reply to#103000
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

[toc] | [prev] | [next] | [standalone]


#103017

Fromsrinivas devaki <mr.eightnoteight@gmail.com>
Date2016-02-16 21:30 +0530
Message-ID<mailman.176.1455638463.22075.python-list@python.org>
In reply to#103012
On Tue, Feb 16, 2016 at 6:35 PM, Sven R. Kunze <srkunze@mail.de> wrote:
>
> First, the rhs is evaluated.
> Second, the lhs is evaluated from left to right.
Great, I will remember these two lines :)

On Tue, Feb 16, 2016 at 8:46 PM, Steven D'Aprano <steve@pearwood.info> wrote:
> _temp = c
> a = _temp
> b = _temp
> del _temp
>
>
> except the name "_temp" isn't actually used.
>
So it is like first right most expression is evaluated and then lhs is
evaluated from left to right.

> 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.
>
thank-you so much, for explaining how to find the underlying details.


>> 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:
>
> 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.
>

uhh, i related it with swap because I was thinking variables are
binded, like first of all for all lhs assignments get their references
or names and then put the value of rhs in them.
as `a` is a name, so the rhs reference is copied to the a
`roots[a]` is a reference to an object, so it is initialized with the
reference of rhs.

anyway I got it, and all my further doubts are cleared from that
compiled code. I tried some other examples and understood how it
works.

thanks a lot.

-- 
Regards
Srinivas Devaki
Junior (3rd yr) student at Indian School of Mines,(IIT Dhanbad)
Computer Science and Engineering Department
ph: +91 9491 383 249
telegram_id: @eightnoteight

[toc] | [prev] | [standalone]


Back to top | Article view | comp.lang.python


csiph-web