Shihao Xu
Shihao Xu

Reputation: 771

What does the operator += return in Python

Original Question

While I was trying to answer another person's question on stackoverflow about the difference between = and += in Python, I encountered the following problem:

class Foo:
    def __init__(self, value, name="default"):
        self.value = value
        self.name = name
        
    def __add__(self, that):
        return Foo(self.value + that.value)
    
    def __iadd__(self, that):
        self.value = self.value + that.value
        return self
    
    def __str__(self):
        return "name: {}, value: {:d}".format(self.name, self.value)
    
a = Foo(1, 'alice')
b = Foo(2, 'bob')
print(a+=b)

The last print call was not successful and gave me this:

File "<ipython-input-8-0faa82ba9e4a>", line 3
    print(a+=b)
            ^
SyntaxError: invalid syntax

I don't know why this isn't working. Maybe it has something to do with the keyword argument passing mechanism? I just can't find any resource on this topic, since the overloaded __iadd__ method already returns a Foo object.

************** update ******************

If I change the __iadd__ method like this (just remove the return statement):

...
    def __iadd__(self, that):
        print("__iadd__ was called")
        self.value = self.value + that.value

a = Foo(1, 'alice')
b = Foo(2, 'bob')
a += b
print(a)  # Outputs: None

So, the final return statement in __iadd__ is indeed required. But it does not function as I thought (I come from a C/C++ background, so this behavior is a bit strange for me)

************************* 2nd Update ********************************

I almost forget that = in Python makes up a statement instead of an expression. The return statement in __iadd__ and my experience with other languages gives me an illusion that += could be used as an expression.

As stated in the Python documentation, __add__ is used to construct a new object. __iadd__ is designed for inplace modifications. But it all depends on the implementation. Although __iadd__ returns a object, this object is somehow "intercepted" by the language and reassigned to the left-hand operator, and, the final effect is, __iadd__ remains a statement, not an expression. Please correct me if I'm wrong. Also, I didn't find any resource confirming that += is a statement.

Summary

  1. A plain code, say a = 1 is an assignment statement. It's not allowed to be used as a function argument. However, keyword argument passing is not restricted by that: print('Hello world', end='') still works.
    • x = x + y is equivalent to x = x.__add__(y),
    • x += y is equivalent to x = x.__iadd__(y), check the doc for more details.
  2. An example:
class Foo:
    def __init__(self, value, name="default"):
        self.value = value
        self.name = name

    def __add__(self, that):
        return Foo(self.value + that.value)

    def __iadd__(self, that):
        self.value = self.value + that.value
        return self

    def __str__(self):
        return "name: {}, value: {:d}".format(self.name, self.value)

a = Foo(1, 'alice')
b = Foo(2, 'bob')
c = a + b # objects a and b are unchanged
a += b    # object a is changed
print(c) # name: default, value: 3
print(a) # name: alice, value: 3

Upvotes: 15

Views: 5512

Answers (4)

SpaceKatt
SpaceKatt

Reputation: 1401

This is a syntax error caused by using an assignment statement as an expression. Here is another SO post which explains the difference between statements and expressions.

It is not a bug with the implementation of the operator overload.

Example of how the syntax results in an error in another context (using int instead of Foo):

a = 2
b = 3

# STILL AN ERROR!
print(a += b) # Error while using the standard int += operator

How to fix the syntax issue:

a = 2
b = 3

a += b      # Separate the assignment statement...
print(a)    #     ...and give the print function an expression

Upvotes: 3

Sam Mason
Sam Mason

Reputation: 16174

The semantics of Python might be confusing matters. As an illustrative example, try working through the following example:

class Foo:
    def __init__(self, value):
        self.value = value

    def __iadd__(self, other):
        self.value = self.value + other.value
        return 'did iadd'

a = Foo(1)
c = a
c += Foo(2)

print((c, a.value))

should result in ('did iadd', 3) being printed

As per the docs:

x += y is equivalent to x = x.__iadd__(y)

The contract for __iadd__ is that you should implement it in such a way that the semantics of x = x + y evaluates to the "same" as x += y. The "same" depends on the details of the problem, e.g. for immutable objects (such as integers or strings) this will necessitate allocating a new object and updating the reference to point to, e.g.:

a = 500  # move out of the "interned" number space
c = a
a += 1
print(id(a), id(c))

should give you two different numbers, which in CPython will be the addresses of the two int objects, both distinct and immutable. For other data types, it can be useful to allow mutable/inplace updates to occur, e.g. for efficiency or other reasons. The semantics of __iadd__ allow for the class implementation to choose a default.

Each language has its own peculiarities, hence I'd suggest not trying to compare it too literally to C++. Especially as C++ has a lot of historical baggage it's being forced to carry along.

Upvotes: 5

Jean-Fran&#231;ois Fabre
Jean-Fran&#231;ois Fabre

Reputation: 140168

Assignments/in-place operations can't be considered as expressions that can be passed on, and that's by design.

Among other things, Guido wanted to avoid the infamous C typo

if (a=b)  // user actually wanted to test a==b

But python 3.8 provides a new assignment method known as assignment expression that allows to pass the assigned value as a parameter (which is very handy in list comprehensions).

You still cannot do in-place add but you can do add then assign:

>> a=2
>> b=3
>> print(a:=a+b)
5

Upvotes: 9

Jacob Lee
Jacob Lee

Reputation: 4680

The += operator is part of a set of multiple Augmented Assignment Operators. You can think of these as a shortcut for Assignment Statements where the variable being assigned is also in the value being assigned to the variable. So, i += 1 is identical to i = i + 1.

Thus, the issue that you are encountering is caused because only values (integer, float, strings, etc.) or expressions can be passed to the print() function. a += b is not a value (it is a statement) and it is not an expression (it does not evaluate to a single value), so you cannot pass it to print().

To solve this:

a = Foo(1, 'alice')
b = Foo(2, 'bob')
a += b
print(a)

Upvotes: 1

Related Questions