[Python-bugs-list] [ python-Bugs-668302 ] a = b += c, a += b = c gives syntax error

SourceForge.net noreply@sourceforge.net
Wed, 15 Jan 2003 14:07:34 -0800


Bugs item #668302, was opened at 2003-01-14 23:45
You can respond by visiting: 
https://sourceforge.net/tracker/?func=detail&atid=105470&aid=668302&group_id=5470

Category: Parser/Compiler
Group: Python 2.2.1
Status: Closed
Resolution: Wont Fix
Priority: 5
Submitted By: L. Peter Deutsch (lpd)
Assigned to: Nobody/Anonymous (nobody)
Summary: a = b += c, a += b = c gives syntax error

Initial Comment:
The new augmented assignment operators apparently are
not syntactically parallel to the assignment operator.
I can't come up with an immediate reason why they
shouldn't be, and I would like them to be.

Python 2.2.1, Red Hat Linux 7.3

I looked in the release notes for Python 2.2.2, and
didn't see anything about this. I also searched the
open bug list for "assignment", and didn't see this
mentioned.


----------------------------------------------------------------------

Comment By: Gregory Smith (gregsmith)
Date: 2003-01-15 17:07

Message:
Logged In: YES 
user_id=292741

I was using 'b' symbolically - if 'b' is something like foo.bar
or foo[bar],  and you apply += to it, then python does a
getattr/getitem  to it, applies += to the result, and then
stores the result of the += back to foo using setattr or
setitem.
This has nothing to do with whether the result of the 'get'
operation has __iadd__ or not; the result of the += still
needs to be stored back, which is a trivial operation when
b is a local variable, but may be more involved when
setattr/setitem is involved.

If 'b' is foo[bar**2], then the bar**2 gets done only once
but two methods get called on foo. I guess it depends on
exactly what we mean by 'evaluated twice',  certainly
+= is handy when the subscript expression is complex

def a():
    foo.bar += 3

dis.dis(a)     # line-nos deleted

          6 LOAD_GLOBAL              0 (foo)
          9 DUP_TOP     # dup foo ref for store_attr later
         10 LOAD_ATTR                1 (bar)      #
getattr(foo,'bar')
         13 LOAD_CONST               1 (3)
         16 INPLACE_ADD      # result of this to be stored
         17 ROT_TWO
         18 STORE_ATTR               1 (bar)  
#setattr(foo,'bar', ...)

         21 LOAD_CONST               0 (None) 
         24 RETURN_VALUE

Hey, suppose you have this in Numeric:
     arr[a:b,c:d,::-1] += something
as discussed, this involves constructing an index (here,
a sequence of slice objects) , doing a getitem (result is a
sliced array view), doing a += on that, and then doing a setitem
with the same slice sequence. The last operation is redundant
if the __iadd__ is implemented, since the result is already in
the original array. I don't know how Numeric handles this,
but it does implement __iadd__. 
So the store following the += would be redundant,
since it would involve copying the same data back over
itself. The Numeric implementation could check for this,
however, it could still be a lot of work to figure out that it
is the case.  It seems to me there should be a way to
avoid this extra work...  the special store-if-not-identical
function mentioned below could be one way, but may
be fraught wth peril, since += could change an object
in ways requiring it to be 'put back' into another container.


anyway, enough for now..


----------------------------------------------------------------------

Comment By: L. Peter Deutsch (lpd)
Date: 2003-01-15 14:40

Message:
Logged In: YES 
user_id=8861

For the augmented assignment b += c, I believe b is *not*
evaluated twice if it has an __iadd__ method.  In fact, I
believe it isn't evaluated twice if it doesn't have such a
method. The *object denoted by the expression b* may be
*accessed* twice (that is, two methods may be invoked) if it
doesn't have an __iadd__ method, but the *expression b
itself* is not *evaluated* twice.

I believe I understand this distinction properly, and I
stand by my previous answer to the question. However,
because of the order issue, I don't think a change should be
made. OTOH, it might be worth adding a note to the Python
documentation for augmented assignment to point out that
unlike ordinary Python assignment, and unlike assignment in
other languages that have both kinds (such as C, C++, and
Java), an augmented assignment must stand alone (cannot be
cascaded with other assignments).


----------------------------------------------------------------------

Comment By: Gregory Smith (gregsmith)
Date: 2003-01-15 14:03

Message:
Logged In: YES 
user_id=292741

The order of ops is not the only problem --

"        b += c
        a = b
except that the targets are only evaluated once.
"
This is the issue here - if 'b' is something like foo[bar]
or foo.bar, then it has to be evaluated three times, once for
reading it, once for writing it, and once for reading it again.
This is true even if the += is done via a __iadd__ method.
 Remember, there is no concept of evaluating '&b' as in C,
which then allows you
to read and write b without having to evaluate &b again.
In Python, b's methods must be used for all accesses 
(assuming b isn't just a variable) so you can't save this
work.

In C, you can write

     foo[e*4+f] += c 
     a = foo[e*4+f]

This is implicitly
    int *p = &foo[e*4+f];
   *p = *p  + c
    a = foo[e*4+f]

  and of course a = foo[e*4+f] += c is implicitly

    int *p = &foo[e*4+f];
   *p = *p  + c
   a = *p // compiler may be able to eliminate the read

You can do sometimes do a similar thing explicitly in 
Python,  as in Numeric:

bslc = b[x:]  # take slice of array starting at x
bslc[0] += c   #  add c to b[x] 
a = bslc[0]    # and copy to a without evaluating x twice

The slice object, which acts a a view on the b array,
is about the closest thing you can get to a dynamically 
computed lvalue, I think.

The semantics of all this generally aren't what they
seem, if you are coming from a C background.
a += b may often change 'a' to a different object.

a = 0
a += 1
here, a += 1 must change 'a' to a different object since
the old '0' object is immutable. In other cases (e.g.
'a' is a list) it is guaranteed to leave 'a' the same object.

foo[m] += x is thus equivalent to
a = foo[m]
anew = a.__iadd__(x)   #a += x; 
#
# (or anew = a+x if you can't __iadd__)
# 'a is anew' may or may not be true now.
#
foo[m] = anew  # put it back.

<<Hey, maybe the semantics of this could be changed
so the last line is conditional:
if a is not anew:
    foo[m] = anew

.. which could be done by a new bytecode,
STORE_SUBSCR_IFISNOT

This would change the semantics of the language a bit,
but unlikely to be harmful unless you're already being weird>>


...
whereas in C++ it would be simply
  operator += ( foo[m], x )
or, to be explicit,
  operator += ( * (foo+m), x )
... where the '*' is not evaluated by the caller (since arg
is a ref).


As you can see, it's not practical to combine the
attribute access with the += operation, as would be
the case in C.


----------------------------------------------------------------------

Comment By: L. Peter Deutsch (lpd)
Date: 2003-01-15 11:20

Message:
Logged In: YES 
user_id=8861

I went back and read the language doc more carefully. I
didn't realize that a = b = c is handled differently from C,
Java, etc. in that after evaluating c, the assignments are
done from left to right -- i.e., there isn't even a
conceptual claim that this is like a = (b = c). So
assignments in Python are even less like expressions than I
had thought.

Nevertheless, I find it really irksome that I can write
        a[big_compllicated_expression] += 1
but I can't write
        x = a[big_complicated_expression] += 1
and instead have to write
        a[big_complicated_expression] = x =
a[big_complicated_expression] + 1
or
        a[big_complicated_expression] += 1
        x = a[big_complicated_expression]
or
        i = big_complicated_expression
        a[i] += 1
        x = a[i]
or
        i = big_complicated_expression
        a[i] = x = a[i] + 1

My suggestion was based on the belief that Python does
multiple assignments like a = b = c right to left. Since it
doesn't, I don't like the answer to "what would I like a = b
+= c to do". Nevertheless, I will answer the question. I
would like a = b += c to be exactly equivalent to
        b += c
        a = b
except that the targets are only evaluated once.

I realize that this amounts to right-to-left assignment
order, and since this is not consistent with the existing
Python order for multiple assignments, I'm willing to
withdraw the request -- for this reason, and this reason only.

Frankly, having understood the situation better, I think
augmented assignment is kind of warty.


----------------------------------------------------------------------

Comment By: Michael Hudson (mwh)
Date: 2003-01-15 06:39

Message:
Logged In: YES 
user_id=6656

What would you have

a = b += c

do?

This has been discussed *somewhere* -- python-list, maybe?

I'm strongly against changing this, fwiw.

----------------------------------------------------------------------

You can respond by visiting: 
https://sourceforge.net/tracker/?func=detail&atid=105470&aid=668302&group_id=5470