Python 3000 idea: reversing the order of chained assignments

M

Marcin Ciura

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
 
B

Ben Finney

Marcin Ciura said:
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?
('baz', 'baz', 'baz')
 
S

Steven D'Aprano

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?

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.
(3, 3, 3)

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

Marcin Ciura

Steven said:
(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
 
T

Terry Reedy

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

John Nagle

Marcin said:
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
 
S

Steve Holden

Marcin said:
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

The difference being ... ?
 
S

Steve Holden

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

... pass
...
False
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
 
A

Alex Martelli

John Nagle said:
Actually, it is equivalent to

y = z
x = y

Not really:
.... 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)
....
As you can see, there is no read-access to c.zop, which plays the role
of y there.


Alex
 
V

Virgil Dupras

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

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

Mark T

Marcin Ciura said:
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.
 
M

Mark T

Alex Martelli said:
John Nagle said:
Actually, it is equivalent to

y = z
x = y

Not really:
... 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)
...
As you can see, there is no read-access to c.zop, which plays the role
of y there.


Alex

This is interesting:
.... 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)
....writing a 1
writing b 2
writing c 3reading 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.
 
T

Terry Reedy

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

Steve Holden

Mark said:
Alex Martelli said:
John Nagle said:
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

This is interesting:
... 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)
...writing a 1
writing b 2
writing c 3reading 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.
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!

regards
Steve
 
S

Steve Holden

Terry said:
| 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

regards
Steve
 
D

Duncan Booth

Virgil Dupras said:
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.
 
S

Steve Holden

Duncan said:
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
 
D

Duncan Booth

Steve Holden said:
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
of the target lists, from left to right".
 
D

Duncan Booth

Steve Holden said:
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.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,769
Messages
2,569,580
Members
45,054
Latest member
TrimKetoBoost

Latest Threads

Top