92

Is it possible to override += in Python?

smci
  • 32,567
  • 20
  • 113
  • 146
Evan Fosmark
  • 98,895
  • 36
  • 105
  • 117

4 Answers4

152

Yes, override the __iadd__ method. Example:

def __iadd__(self, other):
    self.number += other.number
    return self    
hongsy
  • 1,498
  • 1
  • 27
  • 39
John Kugelman
  • 349,597
  • 67
  • 533
  • 578
  • 52
    You shouldn't implement `__iadd__` if your class represents immutable objects. In that case just implement `__add__` which will be used to override `+=` instead. For example you can use `+=` on immutable types such as strings and integers, which couldn't be done using `__iadd__`. – Scott Griffiths Jan 04 '10 at 23:26
  • @ScottGriffiths, so are you saying that if you've implemented `__add__` you don't necessarily have to implement `__iadd__`? I read the duplicate question you linked but I just got confused because I don't understand why you would implement `__add__` so that it mutates the object – Josie Thompson Mar 05 '16 at 22:25
  • 1
    @ScottGriffiths meant ask "you don't necessarily have to implement `__iadd__` __to use +=__?" – Josie Thompson Mar 05 '16 at 22:37
  • 15
    @JosieThompson: If you don't implement `__iadd__` then it will use `__add__` if available, which is usually just fine. So in that case `a += b` would be equivalent to `a = a + b`, which assigns a new value to `a` instead of changing `a` itself. Having a separate `__iadd__` is typically a nice optimisation rather than something you need to use the `+=` operator. – Scott Griffiths Mar 07 '16 at 11:50
  • 1
    @ScottGriffiths Is the same true for `__imul__`? – Chris_Rands Jun 15 '18 at 12:24
  • @Chris_Rands: Yes the same goes for `__imul__` and other in-place operators (`__isub__`, `__idiv__` etc.) They should only be defined for mutable objects, and if not defined all will fall back to the standard operators in the same way. So `a *= 2` is equivalent to `a = a * 2` if `__imul__` is not defined for the type of `a`. – Scott Griffiths Jul 04 '23 at 17:26
46

In addition to what's correctly given in answers above, it is worth explicitly clarifying that when __iadd__ is overriden, the x += y operation does NOT end with the end of __iadd__ method.

Instead, it ends with x = x.__iadd__(y). In other words, Python assigns the return value of your __iadd__ implementation to the object you're "adding to", AFTER the implementation completes.

This means it is possible to mutate the left side of the x += y operation so that the final implicit step fails. Consider what can happen when you are adding to something that's within a list:

>>> x[1] += y # x has two items

Now, if your __iadd__ implementation (a method of an object at x[1]) erroneously or on purpose removes the first item (x[0]) from the beginning of the list, Python will then run your __iadd__ method) & try to assign its return value to x[1]. Which will no longer exist (it will be at x[0]), resulting in an ÌndexError.

Or, if your __iadd__ inserts something to beginning of x of the above example, your object will be at x[2], not x[1], and whatever was earlier at x[0] will now be at x[1]and be assigned the return value of the __iadd__ invocation.

Unless one understands what's happening, resulting bugs can be a nightmare to fix.

Petri
  • 4,796
  • 2
  • 22
  • 31
  • 6
    do you happen to know why `__iadd__` is designed like that? i.e. why it assigns the return value rather than just settling with in-place mutation? – joel Jun 24 '19 at 21:57
  • 4
    @joel Because that's how it must work with immutable types such as `str` and `int`, and, more importantly, `tuple` and `namedtuple`. – Liz Av Apr 07 '21 at 19:53
14

In addition to overloading __iadd__ (remember to return self!), you can also fallback on __add__, as x += y will work like x = x + y. (This is one of the pitfalls of the += operator.)

>>> class A(object):
...   def __init__(self, x):
...     self.x = x
...   def __add__(self, other):
...     return A(self.x + other.x)
>>> a = A(42)
>>> b = A(3)
>>> print a.x, b.x
42 3
>>> old_id = id(a)
>>> a += b
>>> print a.x
45
>>> print old_id == id(a)
False

It even trips up experts:

class Resource(object):
  class_counter = 0
  def __init__(self):
    self.id = self.class_counter
    self.class_counter += 1

x = Resource()
y = Resource()

What values do you expect x.id, y.id, and Resource.class_counter to have?

Community
  • 1
  • 1
  • 11
    Your second example has nothing to do with iadd or +=. The same result occurs if you use self.class_counter = self.class_counter + 1 It's just a scoping issue, using self when Resource should be used. – FogleBird Jun 26 '09 at 02:42
  • It's an example of how using += can lead to problems. If you're overloading __iadd__, then you're opening users of your class (including yourself) to this, and, at the very least, you should know the issue exists beforehand. –  Jun 26 '09 at 04:06
  • 5
    @FogleBird: It is a gotcha because `foo += bar` can either mean "mutate the existing object that `foo` refers to" or "assign `foo` to the object resulting from the expression `foo + bar`". And which happens depends on whether `foo` has an `__iadd__` method. – Claudiu Dec 18 '16 at 07:34
  • In your example, `foo` is just a number. It's not an instance of the class you're defining. You never call either `+` or `+=` with the `Resource` class. It's a gotcha, but it's not related to having or not having a `__iadd__` method defined or not defined for `Resource`. – John Nov 14 '22 at 16:36
5

http://docs.python.org/reference/datamodel.html#emulating-numeric-types

For instance, to execute the statement x += y, where x is an instance of a class that has an __iadd__() method, x.__iadd__(y) is called.

Unknown
  • 45,913
  • 27
  • 138
  • 182