Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Python 3000 idea: reversing the order of chained assignments

6 views
Skip to first unread message

Marcin Ciura

unread,
Mar 21, 2007, 5:53:55 PM3/21/07
to
Given
class Node(object):
pass

node = Node()
nextnode = Node()

I tried to refactor the following piece of code
node.next = nextnode
node = nextnode

as
node = node.next = nextnode

only to discover that Python performs chained assignments
backwards compared to other languages, i.e. left-to-right
instead of right-to-left. From the user's perspective,
I can't think of any reasonable argument for keeping it
this way in Python 3000. What is your opinion?
Marcin

Ben Finney

unread,
Mar 21, 2007, 6:24:33 PM3/21/07
to pytho...@python.org
Marcin Ciura <marcin...@poczta.NOSPAMonet.pl> writes:

> node = node.next = nextnode
>
> only to discover that Python performs chained assignments
> backwards compared to other languages, i.e. left-to-right
> instead of right-to-left.

What makes you think so?

>>> a = "foo"
>>> b = "bar"
>>> c = "baz"
>>> a = b = c
>>> a, b, c
('baz', 'baz', 'baz')

--
\ "During my service in the United States Congress, I took the |
`\ initiative in creating the Internet." -- Al Gore |
_o__) |
Ben Finney

Steven D'Aprano

unread,
Mar 21, 2007, 6:28:45 PM3/21/07
to

Well, this user's perspective is that if I read from left to right, which
I do, then I expect most operations to also go from left to right, and not
from right to left.

Assignment is one exception to that. If I say "x = y = z" then I expect
that afterwards x and y and z should all have the same value.

>>> x, y, z = 1, 2, 3
>>> x, y, z
(1, 2, 3)
>>> x = y = z
>>> x, y, z
(3, 3, 3)

I certainly wouldn't expect to get (2, 3, 3).

--
Steven.

Marcin Ciura

unread,
Mar 21, 2007, 6:42:45 PM3/21/07
to
Steven D'Aprano wrote:
>>>>x, y, z = 1, 2, 3
>>>>x = y = z
>>>>x, y, z
>
> (3, 3, 3)
>
> I certainly wouldn't expect to get (2, 3, 3).

Neither would I. I must have expressed myself not clearly enough.
Currently


x = y = z

is roughly equivalent to
x = z
y = z
I propose to change it to
y = z
x = z
Cheers,
Marcin

Terry Reedy

unread,
Mar 21, 2007, 7:45:14 PM3/21/07
to pytho...@python.org

"Marcin Ciura" <marcin...@poczta.NOSPAMonet.pl> wrote in message
news:etscd5$g0g$1...@news.onet.pl...

| Neither would I. I must have expressed myself not clearly enough.
| Currently
| x = y = z
| is roughly equivalent to
| x = z
| y = z

except that z is only evaluated once.

| I propose to change it to
| y = z
| x = z

Unless target expression expressions interact, there is no difference.
When there are such side effects, I think it best to be explicit about what
you want to happen first, as in your original code, instead of trying to
mash them together in one line.

Terry Jan Reedy

John Nagle

unread,
Mar 21, 2007, 8:17:07 PM3/21/07
to
Marcin Ciura wrote:

> Neither would I. I must have expressed myself not clearly enough.
> Currently
> x = y = z
> is roughly equivalent to
> x = z
> y = z
> I propose to change it to
> y = z
> x = z

Actually, it is equivalent to

y = z
x = y

> Python performs chained assignments backwards compared to other languages...

C and C++ are both right-to-left, like Python. In Pascal,
Modula I/II/III, and Ada, assignments are not expressions, so it's not
a meaningful question there.

John Nagle

Steve Holden

unread,
Mar 21, 2007, 9:24:04 PM3/21/07
to pytho...@python.org

The difference being ... ?
--
Steve Holden +44 150 684 7255 +1 800 494 3119
Holden Web LLC/Ltd http://www.holdenweb.com
Skype: holdenweb http://del.icio.us/steve.holden
Recent Ramblings http://holdenweb.blogspot.com

Virgil Dupras

unread,
Mar 21, 2007, 9:56:02 PM3/21/07
to

I think I see what Marcin means. The 'node' is changed too fast in the
chain, and next is assigned to 'nextnode' instead of being assigned to
node.

>>> class Node:
... pass
...


>>> node = Node()
>>> nextnode = Node()

>>> backup_node = node


>>> node = node.next = nextnode

>>> node.next is node
True
>>> hasattr(backup_node,'next')
False

Alex Martelli

unread,
Mar 21, 2007, 10:06:56 PM3/21/07
to
John Nagle <na...@animats.com> wrote:

> Marcin Ciura wrote:
>
> > Neither would I. I must have expressed myself not clearly enough.
> > Currently
> > x = y = z
> > is roughly equivalent to
> > x = z
> > y = z
> > I propose to change it to
> > y = z
> > x = z
>
> Actually, it is equivalent to
>
> y = z
> x = y

Not really:

>>> class chatty(object):
... def __init__(self): self.__dict__['__hide'] = {}
... def __setattr__(self, name, value):
... print 'sa', name, value
... self.__dict__['__hide'][name] = value
... def __getattr__(self, name):
... print 'ga', name
... return self.__dict__['__hide'].get(name)
...
>>> c = chatty()
>>> x = c.zop = 23
sa zop 23
>>>

As you can see, there is no read-access to c.zop, which plays the role
of y there.


Alex

Steve Holden

unread,
Mar 21, 2007, 10:05:01 PM3/21/07
to pytho...@python.org
So we should take the already well-defined semantics of assignment and
change them because it seems more obvious to J. Random User? I think I
might be a little concerned about potential code breakage there.

regards
Steve

Virgil Dupras

unread,
Mar 21, 2007, 10:13:42 PM3/21/07
to

I totally agree. This assignment is way too ambiguous anyway.
Readability first. I just wanted to enlighten the readers on what the
problem actually was since it wasn't obvious in the original post.

Mark T

unread,
Mar 22, 2007, 12:09:52 AM3/22/07
to

"Marcin Ciura" <marcin...@poczta.NOSPAMonet.pl> wrote in message
news:ets9hc$74f$1...@news.onet.pl...

> Given
> class Node(object):
> pass
>
> node = Node()
> nextnode = Node()
>
> I tried to refactor the following piece of code
> node.next = nextnode
> node = nextnode

You have an error above. The first node's "next" points to nextnode, then
node is reassigned to point to nextnode, losing the original node!
The refactoring below is doing the same thing. It is, however, evaluating
right-to-left as you want. nextnode is place in node.next and placed in
node, also losing the original value of node.

-Mark T.

Mark T

unread,
Mar 22, 2007, 12:36:10 AM3/22/07
to

"Alex Martelli" <al...@mac.com> wrote in message
news:1hvcadb.134pbvp1y39wi3N%al...@mac.com...

This is interesting:

>>> class Test(object):
... def __getattribute__(self,n):
... print 'reading',n
... return object.__getattribute__(self,n)
... def __setattr__(self,n,v):
... print 'writing',n,v
... return object.__setattr__(self,n,v)
...
>>> x=Test()
>>> x.a=1; x.b=2; x.c=3
writing a 1
writing b 2
writing c 3
>>> x.a=x.b=x.c
reading c
writing a 3
writing b 3
>>>

I wouldn't have expected "a" to be assigned first in a right-to-left parsing
order. The result is the same in any case.

-Mark T.

Terry Reedy

unread,
Mar 22, 2007, 1:51:31 AM3/22/07
to pytho...@python.org

"Mark T" <nos...@nospam.com> wrote in message
news:DvGdnQacTuMlmp_b...@comcast.com...

| This is interesting:
|
| >>> class Test(object):
| ... def __getattribute__(self,n):
| ... print 'reading',n
| ... return object.__getattribute__(self,n)
| ... def __setattr__(self,n,v):
| ... print 'writing',n,v
| ... return object.__setattr__(self,n,v)
| ...
| >>> x=Test()
| >>> x.a=1; x.b=2; x.c=3
| writing a 1
| writing b 2
| writing c 3
| >>> x.a=x.b=x.c
| reading c
| writing a 3
| writing b 3
| >>>
|
| I wouldn't have expected "a" to be assigned first in a right-to-left
parsing
| order. The result is the same in any case.

The assignment order is specified in the language reference.
But many never read and those who do can forget.
And even if the coder reads and remembers, a code reader may not have.
Which is why I suggested multiple statements in explicit order when it
really matters.

tjr

Steve Holden

unread,
Mar 22, 2007, 4:48:27 AM3/22/07
to pytho...@python.org
It would be nice if your confusion could have been avoided by reading
the reference manual, but unfortunately the current syntax definition
doesn't seem to even acknowledge the possibility of multiple assignments
such as the one under discussion!

Steve Holden

unread,
Mar 22, 2007, 4:54:02 AM3/22/07
to pytho...@python.org
Terry Reedy wrote:
> "Mark T" <nos...@nospam.com> wrote in message
> news:DvGdnQacTuMlmp_b...@comcast.com...
> | This is interesting:
> |
> | >>> class Test(object):
> | ... def __getattribute__(self,n):
> | ... print 'reading',n
> | ... return object.__getattribute__(self,n)
> | ... def __setattr__(self,n,v):
> | ... print 'writing',n,v
> | ... return object.__setattr__(self,n,v)
> | ...
> | >>> x=Test()
> | >>> x.a=1; x.b=2; x.c=3
> | writing a 1
> | writing b 2
> | writing c 3
> | >>> x.a=x.b=x.c
> | reading c
> | writing a 3
> | writing b 3
> | >>>
> |
> | I wouldn't have expected "a" to be assigned first in a right-to-left
> parsing
> | order. The result is the same in any case.
>
> The assignment order is specified in the language reference.

Where? I'm looking at

http://docs.python.org/ref/assignment.html

right now.

> But many never read and those who do can forget.
> And even if the coder reads and remembers, a code reader may not have.
> Which is why I suggested multiple statements in explicit order when it
> really matters.
>

The current docs define

a, b = c, d

and

a = b, c, d

and

a, b, c = d

as valid assignments, but I can't find the part where

a = b = c

is defined. Help me out here. It looks as though the real syntax should
be something like

assignment_stmt ::= (target_list "=")+ expression_list |
(target_list "=")+ assignment_stmt

Duncan Booth

unread,
Mar 22, 2007, 5:03:45 AM3/22/07
to
"Virgil Dupras" <hardcoded...@gmail.com> wrote:

> I think I see what Marcin means. The 'node' is changed too fast in the
> chain, and next is assigned to 'nextnode' instead of being assigned to
> node.

I can see why Marcin was confused. Many other languages assignment is an
expression, so a=b=c is simply a=(b=c). In Python it isn't an expression,
the chained assignment is a specific part of the syntax, so (as with
chained comparisons) the semantics may be surprising to the uninitiated.

As a matter of interest do PyLint or PyChecker check for this situation
(chained assignment where the target of an assignment is also a
subexpression of a later assignment)?

Also it may be worth noting that unpacking has a similar behaviour:

node, node.next = nextnode, nextnode

has the same result as the chained assignment: the RHS is a tuple and is
fully evaluated before the assignment, but the LHS is not a tuple and the
assignment happens strictly left to right with each assignment fully
completed before proceeding to the next one.

Steve Holden

unread,
Mar 22, 2007, 5:55:03 AM3/22/07
to pytho...@python.org
Duncan Booth wrote:
> "Virgil Dupras" <hardcoded...@gmail.com> wrote:
>
>> I think I see what Marcin means. The 'node' is changed too fast in the
>> chain, and next is assigned to 'nextnode' instead of being assigned to
>> node.
>
> I can see why Marcin was confused. Many other languages assignment is an
> expression, so a=b=c is simply a=(b=c). In Python it isn't an expression,
> the chained assignment is a specific part of the syntax, so (as with
> chained comparisons) the semantics may be surprising to the uninitiated.
>
> As a matter of interest do PyLint or PyChecker check for this situation
> (chained assignment where the target of an assignment is also a
> subexpression of a later assignment)?
>
Where's the published syntax for chained assignment?

> Also it may be worth noting that unpacking has a similar behaviour:
>
> node, node.next = nextnode, nextnode
>
> has the same result as the chained assignment: the RHS is a tuple and is
> fully evaluated before the assignment, but the LHS is not a tuple and the
> assignment happens strictly left to right with each assignment fully
> completed before proceeding to the next one.
>

Well that is explained (albeit badly) in the reference manual:

"""An assignment statement evaluates the expression list (remember that
this can be a single expression or a comma-separated list, the latter
yielding a tuple) and assigns the single resulting object to each of the
target lists, from left to right."""

That wording appears to be a rather bad mix of factoids from unpacking
and chained assignment. Is it just me, or does this whole section need a
rewrite?

regards
Steve

Duncan Booth

unread,
Mar 22, 2007, 9:23:08 AM3/22/07
to
Steve Holden <st...@holdenweb.com> wrote:

>> As a matter of interest do PyLint or PyChecker check for this situation
>> (chained assignment where the target of an assignment is also a
>> subexpression of a later assignment)?
>>
> Where's the published syntax for chained assignment?

http://docs.python.org/ref/assignment.html

More specifically, since you seem to have missed it, it's the '+' in the
line:

assignment_stmt ::= (target_list "=")+ expression_list

And then the clear statement "assigns the single resulting object to each

Duncan Booth

unread,
Mar 22, 2007, 9:30:19 AM3/22/07
to
Steve Holden <st...@holdenweb.com> wrote:

> Help me out here. It looks as though the real syntax should
> be something like
>
> assignment_stmt ::= (target_list "=")+ expression_list |
> (target_list "=")+ assignment_stmt

That is precisely the point. If it was:

assignment_stmt ::= (target_list "=") expression_list |
(target_list "=") assignment_stmt

(i.e. removing the '+' which your eyes jumped over)
then the actual assignments would have to apply right to left with each
assignment giving the result for the next one.

But since it is:

assignment_stmt ::= (target_list "=")+ expression_list

the repeated target lists may be expected, and are indeed defined, to
assign left to right.

Steve Holden

unread,
Mar 22, 2007, 11:21:04 AM3/22/07
to pytho...@python.org

Got it, thanks. See my further remarks in my reply to your last.

Steve Holden

unread,
Mar 22, 2007, 11:23:25 AM3/22/07
to pytho...@python.org

Thanks, I see the plus sign now and appreciate that it indicates "one or
more of", so the syntax is correct. But syntax doesn't imply semantics,
so a left-recursive or right-recursive syntax formulation wouldn't
require any change to the semantics of assignment.

In other words,

assignment_stmt ::= (target_list "=") expression_list |
(target_list "=") assignment_stmt

and

assignment_stmt ::= (target_list "=") assignment_stmt |
(target_list "=") expression_list

are entirely equivalent, and neither imply any order of execution. I'm
sure you understand that syntax only specifies what's legal, not how it
should be interpreted.

Duncan Booth

unread,
Mar 22, 2007, 12:17:16 PM3/22/07
to
Steve Holden <st...@holdenweb.com> wrote:

> In other words,
>
> assignment_stmt ::= (target_list "=") expression_list |
> (target_list "=") assignment_stmt
>
> and
>
> assignment_stmt ::= (target_list "=") assignment_stmt |
> (target_list "=") expression_list
>
> are entirely equivalent

I'm not quite sure what you are getting at. An assigment_stmt and an
expression_list are not ambiguous so those two productions are identical
(not just equivalent). Perhaps you are thinking of productions like:

m_expr ::= u_expr | m_expr "*" u_expr

which will match the same input, but could produce a different parser
output than:

m_expr ::= u_expr | u_expr "*" m_expr


> I'm sure you understand that syntax only specifies what's legal, not how
> it should be interpreted.

I agree that the syntax does not mandate how it should be interpreted,
but it does lead to expectations.

If the expression_stmt is hidden from the outer assignment_stmt by an inner
one then it is reasonable to expect that the inner production will be
completely evaluated before the outer assignment happens. i.e. right to
left.

John Nagle

unread,
Mar 22, 2007, 12:21:12 PM3/22/07
to

That's fascinating. Is that a documented feature of the language,
or a quirk of the CPython interpreter?

John Nagle

Ziga Seilnacht

unread,
Mar 22, 2007, 12:29:00 PM3/22/07
to
John Nagle wrote:
>
> That's fascinating. Is that a documented feature of the language,
> or a quirk of the CPython interpreter?
>
Its a documented feature of the language. From the Reference Manual:

"An assignment statement evaluates the expression list (remember that
this can be a single expression or a comma-separated list, the latter

yielding a tuple) and assigns the single resulting object to each of


the target lists, from left to right."

See: http://docs.python.org/ref/assignment.html

Ziga


Erik Johnson

unread,
Mar 22, 2007, 1:22:40 PM3/22/07
to

"Virgil Dupras" <hardcoded...@gmail.com> wrote in message
news:1174529622.8...@d57g2000hsg.googlegroups.com...

> > >>>> class Node:
> > > ... pass
> > > ...
> > >>>> node = Node()
> > >>>> nextnode = Node()
> > >>>> backup_node = node
> > >>>> node = node.next = nextnode
> > >>>> node.next is node
> > > True
> > >>>> hasattr(backup_node,'next')
> > > False


Well, I think I am thoroughly confused now. I am a lot less experienced
that most of the people posting in this thread, so perhaps that is to be
expected, but I actually thought I understood Python fairly well. Maybe I am
fooling myself...

Let me rename some variables in the code above to something a little
easier to follow:

>>> class Node: pass
...
>>> n1 = Node()
>>> n2 = Node()
>>> n3 = n1

At this point I beleive I've got the same thing above, just with different
reference names. Namely, I have two objects, and three references, the
first and third reference now both referring to the first object, the second
reference referring to the second object:

>>> n1
<__main__.Node instance at 0x7ff1d20c>
>>> n2
<__main__.Node instance at 0x7ff1d10c>
>>> n3
<__main__.Node instance at 0x7ff1d20c>

The discussion is about multiple assignments. Virgil's example should be
equivalent to:

n1 = n1.next = n2

If assignment were left to right, (i.e., n1 = n1.next, followed by
n1.next = n2), then I would expect to get an attribute error because n1
hasn't had the 'next' attribute attached to it yet. That's not what
happens, so the other interpretation is that the statement above is
equivalent to: n1.next = n2; n1= n1.next (except that n1.next is only
evaluated once, but that doesn't matter here). Right? That is, first object
'n1' gets a new attribute, the value of which is a reference to object n2,
and then, the name 'n1' is rebound to be a reference to object n2 (note that
the object n1 was previously referencing should still be available via name
'n3', and being a mutable object, the new attribute should be visible via
'n3', right?

I didn't yet execute the statement above (I just typed it in this
post) - let's first check the objects and attributes for what we have prior
to this confusing statement:

>>> n1
<__main__.Node instance at 0x7ff1d20c>
>>> n2
<__main__.Node instance at 0x7ff1d10c>
>>> n3
<__main__.Node instance at 0x7ff1d20c>
>>> dir(n1)
['__doc__', '__module__']
>>> dir(n2)
['__doc__', '__module__']
>>> dir(n3)
['__doc__', '__module__']

Right... no suprises there. Let's execute that funky statement...

>>> n1 = n1.next = n2
>>>

We would expect n1 to reference n2 now (my object at ...d10c), which it
does:

>>> n1
<__main__.Node instance at 0x7ff1d10c>

And we would expect n3 to still be referencing the object at ...d20c, which
it also still does:

>>> n3
<__main__.Node instance at 0x7ff1d20c>

And we would expect (or I should say "I would expect") n3 to now have a
'next' attribute:

>>> dir(n3)
['__doc__', '__module__']

It doesn't, which is what Virgil previously pointed out. I'm not quite
following the whole discussion (nor the syntax diagrams), so sorry if this
was already explined - could someone try again: why is it that n3 here
doesn't get the 'next' attribute?

Now here's the part that totally floors me: what would you expect to be
the attributes on n2 (the object on the far-right of the multi-way
assignment statement)? We've made no assigment statements to anything about
n2. They should be the same as before, right?

>>> dir(n2)
['__doc__', '__module__', 'next']
>>> n2.next
<__main__.Node instance at 0x7ff1d10c>
>>> n2
<__main__.Node instance at 0x7ff1d10c>
>>>

DUH.... I'm about speechless... Is it just me being dense, or is there some
obvious reason why one would expect this?


Erik Johnson

unread,
Mar 22, 2007, 2:04:35 PM3/22/07
to
Actually, after studying this a bit more:
http://docs.python.org/ref/assignment.html

I guess that makes sense. Sorry if I muddied the water for anyone else in my
boat:

n1 = n1.next = n2

The first thing that happens is the expression list is evaluated which is
the thing on the far right, n2. That is a simple object reference which is
then assigned to each of the target lists, left to right, of which there are
two: n1 and n1.next.

So, first, n1 is assigned the same value n2 has.

Next, n1.next is assigned n2 (The object n1 refers to, which is also now n2,
is assigned a new attribute, that value of which is n2).

So, yeah... as Terry Reedy said: better to be explicit about what you want
to happen first and not mash them together into one line.

That would be... how do you say... "Pythonic"?

(God, I feel so much better now. LOL)


Terry Reedy

unread,
Mar 22, 2007, 2:42:48 PM3/22/07
to pytho...@python.org

"Steve Holden" <st...@holdenweb.com> wrote in message
news:ettg9s$t94$1...@sea.gmane.org...

| Terry Reedy wrote:
| > The assignment order is specified in the language reference.
|
| Where? I'm looking at
|
| http://docs.python.org/ref/assignment.html
|
| right now.

The first line of the syntax grammar is:


assignment_stmt ::= (target_list "=")+ expression_list

The '+' means 1 or more 'target_list =' occurances.

The first line of the semantic explanation is:

" An assignment statement evaluates the expression list (...) and assigns
the single resulting object to each of the target lists, from left to
right."

Left to right.

And one can also check the byte code:
>>> dis.dis(compile('a=b=c', '', 'single'))
1 0 LOAD_NAME 0 (c)
3 DUP_TOP
4 STORE_NAME 1 (a)
7 STORE_NAME 2 (b)
10 LOAD_CONST 0 (None)
13 RETURN_VALUE

| as valid assignments, but I can't find the part where
|
| a = b = c
|

| is defined. Help me out here. It looks as though the real syntax should


| be something like
|
| assignment_stmt ::= (target_list "=")+ expression_list |
| (target_list "=")+ assignment_stmt

You only need the first line (see above).

Terry Jan Reedy

0 new messages