__add__ / __radd__ / __iadd__
def __add__(self, other) Any · Added in v3.0 · Updated March 20, 2026 · Dunder Methods Overview
Three dunder methods control addition behaviour on custom classes:
__add__— called forself + other__radd__— called as a fallback when the left operand does not support the operation__iadd__— called forself += other
All three are available in Python 3.0 with no import required.
When Python evaluates a + b, it first tries a.__add__(b). If that returns NotImplemented, Python then tries b.__radd__(a).
__add__ — Left-side addition
Implements the binary + operator for self + other. Return the result of the operation, or return NotImplemented to signal that the operation is not supported.
class Vector:
"""A 2D vector with x and y components."""
def __init__(self, x, y):
self.x = x
self.y = y
def __add__(self, other):
if not isinstance(other, Vector):
return NotImplemented
return Vector(self.x + other.x, self.y + other.y)
def __repr__(self):
return f"Vector({self.x}, {self.y})"
v1 = Vector(1, 2)
v2 = Vector(3, 4)
print(v1 + v2)
# output: Vector(4, 6)
Return NotImplemented, not an exception. Raising TypeError short-circuits the protocol and Python will not then try other.__radd__(self).
__radd__ — Right-side (reflected) addition
Python calls __radd__ as a fallback when other.__add__(self) returns NotImplemented. The evaluation chain for a + b is:
a.__add__(b)— tried first- If it returns
NotImplemented, thenb.__radd__(a)
A common use case is allowing int + MyClass to work when int.__add__ does not know how to add a MyClass instance. The typical implementation delegates back to __add__:
class Money:
def __init__(self, amount):
self.amount = amount
def __add__(self, other):
if isinstance(other, Money):
return Money(self.amount + other.amount)
if isinstance(other, int):
return Money(self.amount + other)
return NotImplemented
def __radd__(self, other):
return self.__add__(other)
def __repr__(self):
return f"${self.amount}"
print(Money(10) + 5) # output: $15
print(5 + Money(10)) # output: $15
__radd__ is not called automatically for every other + self. Python only calls it when the left operand’s __add__ explicitly returns NotImplemented. If both operands define __add__ but neither knows about the other, Python raises TypeError.
__iadd__ — In-place addition
Implements the += operator for self += other. For mutable objects, this should mutate self in place and return self. For immutable objects, returning a new object is acceptable.
class Counter:
def __init__(self, count=0):
self.count = count
def __iadd__(self, other):
self.count += other
return self
def __repr__(self):
return f"Counter({self.count})"
c = Counter(10)
c += 5
print(c)
# output: Counter(15)
Must return self. Forgetting to return from __iadd__ binds the variable to None after +=, because the augmented assignment statement performs an assignment after calling the dunder method.
If __iadd__ is not defined, Python falls back to __add__ to implement +=, which creates a new object. For mutable types, defining __iadd__ avoids unnecessary copies.
Gotchas
Return NotImplemented, not an exception.
Inside __add__ or __radd__, never raise TypeError to indicate an unsupported type. Raise nothing — return NotImplemented. Raising an exception prevents Python from trying the reflected method:
class Bad:
def __add__(self, other):
raise TypeError("unsupported") # wrong — prevents __radd__
# Python will NOT call __radd__ on the other operand
__iadd__ must return self.
Omitting the return causes surprising behaviour:
class Broken:
def __iadd__(self, other):
self.value += other
# missing return
b = Broken()
b += 10
print(b) # output: None — b is now None
Mutable default trap.
Using a mutable object as a default argument shares that object across all instances:
class BrokenList:
def __init__(self, data=[]):
self.data = data
def __iadd__(self, other):
self.data.append(other)
return self
a = BrokenList()
a += 1
b = BrokenList()
print(b.data) # output: [1] — shared state between a and b
Use data=None and assign inside the body instead:
class FixedList:
def __init__(self, data=None):
self.data = data if data is not None else []
def __iadd__(self, other):
self.data.append(other)
return self
See Also
__hash__— Hashing protocol; Python sets__hash__ = Nonewhen you define__eq__without `hash__enter__/__exit__— Context manager protocol- Python Docs: Emulating Numeric Types