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
> 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
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.
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
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
> 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
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
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
> 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
regards
Steve
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.
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.
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.
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
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
> 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.
> 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
>> 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
> 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.
Got it, thanks. See my further remarks in my reply to your last.
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.
> 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.
That's fascinating. Is that a documented feature of the language,
or a quirk of the CPython interpreter?
John Nagle
"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
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?
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)
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