Back to Blog
Python

Python __add__: Overloading the + Operator

python **add**: Learn how Python's __add__ method powers the + operator, how to implement it correctly, and how __radd__ and __iadd__ extend addition semantics.

operator overloadingdunder methodsPython data modelspecial methodsNotImplemented
Editorial illustration of the Python + operator connecting two objects through the __add__ special method

The phrase python add usually points to one thing: the __add__ special method that powers the + operator. When you write a + b, Python checks whether a has an __add__ method and calls it with b as the argument. This is the mechanism behind operator overloading in Python's data model, and it is what lets custom types support addition with the same syntax as built-in types.

What __add__ Does and When to Implement It

__add__ is part of Python's special method protocol. It is invoked by the binary + operator, and its return value becomes the result of the expression. If the left operand does not define __add__, or if __add__ returns NotImplemented, Python falls back to the right operand's __radd__ method. If neither side can handle the operation, Python raises TypeError.

You need __add__ whenever your type has a natural addition operation. Typical examples are numeric wrappers, vectors, matrices, monetary amounts, or domain objects where combining two instances produces a meaningful result. Without __add__, using + on your object raises TypeError: unsupported operand type(s) for +.

A Minimal __add__ Implementation

The simplest correct implementation follows the shape of built-in types: read the operands, compute the result, return a new instance.

class Vector: def __init__(self, x, y): self.x = x self.y = y def __add__(self, other): return Vector(self.x + other.x, self.y + other.y)

This works when other is also a Vector. The method reads x and y from other, computes the sums, and returns a brand-new Vector. Note that self is never modified. That is a core contract: __add__ produces a new object rather than mutating the left operand. If you mutate self and return it, callers who expected a fresh value will get surprising aliasing behavior.

Handling Type Mismatches in __add__

The minimal version breaks when other is not a Vector. Calling vector + 5 raises AttributeError because int has no x attribute. The correct way to reject unsupported operands is to return the singleton NotImplemented:

def __add__(self, other): if not isinstance(other, Vector): return NotImplemented return Vector(self.x + other.x, self.y + other.y)

Returning NotImplemented tells Python that this operand pair is not supported. Python then tries the reflected operation on the right operand, and if that also returns NotImplemented, Python raises TypeError with a descriptive message. This is the standard pattern, and it is what built-in types do internally. Returning False or None instead is a common bug: Python will treat the result as the expression's value, silently producing wrong behavior.

Reflected Addition with __radd__

When the left operand cannot handle the addition, Python tries __radd__ on the right operand. Consider 5 + vector. The int type has no knowledge of Vector, so Python calls Vector.__radd__(vector, 5). Without __radd__, this expression raises TypeError.

def __radd__(self, other): return self.__add__(other)

For commutative operations, delegating to __add__ is correct. For non-commutative operations, __radd__ must implement the proper logic. Note the argument order: __radd__ receives the left operand as other, so the operation being expressed is other + self.

In-Place Addition with __iadd__

The += operator dispatches to __iadd__ when it is defined. Without __iadd__, Python falls back to __add__ and rebinds the name to the returned result. For immutable types, that fallback is exactly right. For mutable types, __iadd__ allows in-place modification:

class Buffer: def __init__(self, items): self.items = list(items) def __iadd__(self, other): self.items.extend(other.items) return self

Returning self is required so that the augmented assignment rebinds correctly. If you return a different object, a += b will rebind a to that new object, which may or may not be what you intend. For an immutable value type, the idiomatic choice is to omit __iadd__ entirely and let the __add__ fallback handle +=.

Return Type Discipline: New Object vs. Mutation

The distinction between __add__ and __iadd__ reflects a design decision about your type. __add__ should not mutate either operand; it should produce a new instance. This matches the semantics of immutable types like int and str. If your type is mutable and + is expected to mutate, you are working against Python's conventions, and callers will be surprised when the original object changes.

For a mutable collection, __iadd__ mutating in place is idiomatic. For an immutable value type, __iadd__ should simply delegate to __add__ and return the new object. The decision depends on whether your type models a value or a mutable container. Document the choice in the class docstring so callers know what to expect.

Performance and Maintainability Considerations

__add__ runs on every + operation, so in tight loops the cost of attribute access and object construction is real. Creating a new Vector for each addition allocates a new object. For large numeric workloads, consider whether a plain tuple or a library like NumPy is a better fit. There is no reason to micro-optimize a __add__ that runs occasionally, but if it appears in a hot path, measure before changing the design.

From a maintainability perspective, keep __add__ small. If __add__ and __radd__ share logic, extract a private helper method so the two entry points stay consistent. If the addition logic is complex, delegate to a named method rather than inlining everything in the dunder. This keeps the operator surface readable and testable.

Common Mistakes and Edge Cases

A frequent mistake is returning False or None from __add__ when the operand type is wrong. The correct return is NotImplemented. Returning False makes Python treat the result as a boolean, silently producing wrong behavior in arithmetic expressions.

Another edge case involves subclass checks. isinstance(other, Vector) accepts subclasses, which is usually what you want. If you need exact type matching, use type(other) is Vector, but that is rarely necessary and can break legitimate subclass usage.

Finally, remember that __add__ is not limited to numeric types. You can define + for strings, lists, or any domain object where the operator has a clear meaning. The same dispatch rules apply, and the same NotImplemented pattern keeps your implementation compatible with Python's operator protocol.

python **add**: Practical Usage and Code Examples | RYUSLOG DEV