Learn how to use Python’s arithmetic magic methods like __add__, __sub__, __mul__, and more to make your classes behave like numbers.
Have you ever wished your Python objects could behave like numbers? What if you could add two custom objects with +
, or even write my_obj *= 2
and have it make perfect sense? Well, Python lets you do that and it’s all thanks to arithmetic magic methods like __add__
, __sub__
, and __mul__
.
In this blog, we’ll go from basics to advanced, with examples you can use right away. We'll also talk about the hidden dangers of these features and how to use them properly.
These are special methods in Python that let your custom objects respond to built-in arithmetic operators like +
, -
, *
, /
, **
, and so on. You’ve probably seen methods like __init__
, __str__
, or __repr__
. Well, __add__
and its siblings are in the same family, they just deal with math.
Operator | Method | Example |
---|---|---|
+ |
__add__ |
a + b |
- |
__sub__ |
a - b |
* |
__mul__ |
a * b |
/ |
__truediv__ |
a / b |
// |
__floordiv__ |
a // b |
% |
__mod__ |
a % b |
** |
__pow__ |
a ** b |
There are also reflected versions like __radd__
, and in-place ones like __iadd__
.
Let’s say you’re writing a class for money, like Money(10, 'USD')
. It’d be cool if you could add two money objects like this:
usd1 = Money(10, 'USD')
usd2 = Money(5, 'USD')
print(usd1 + usd2) # Should print: 15 USD
Out of the box, Python has no idea what +
means for your custom class. That’s where __add__
comes in. By defining it, you teach Python: “Here’s what it means to add two of my objects.” So instead of writing clunky methods like money.add(other)
, you can use clean, readable expressions like money1 + money2
.
Let’s walk through a real case step-by-step.
__add__
class Money:
def __init__(self, amount, currency):
self.amount = amount
self.currency = currency
def __str__(self):
return f"{self.amount} {self.currency}"
def __add__(self, other):
if self.currency != other.currency:
raise ValueError("Can't add different currencies!")
return Money(self.amount + other.amount, self.currency)
m1 = Money(10, 'USD')
m2 = Money(5, 'USD')
print(m1 + m2) # 15 USD
Money
objects: m1
has 10 USD and m2
has 5 USD.m1 + m2
, Python internally calls m1.__add__(m2)
.__add__
, we:
10 + 5 = 15
.Money
object with 15 USD.__str__
method controls how the object is displayed when printed, so it shows "15 USD"
.This example shows how to safely define the meaning of +
for a class and avoid invalid operations like mixing currencies.
__radd__
for int + Money
class Money:
...
def __radd__(self, other):
if isinstance(other, int):
return Money(self.amount + other, self.currency)
return NotImplemented
print(5 + m1) # 15 USD
5 + m1
would fail, because Python tries to call int.__add__(m1)
but int
doesn’t know how to handle a Money
object.m1.__radd__(5)
.__radd__
:
other
) is an int
.self.amount
.Money
object with the updated amount.This lets users add raw numbers to your custom objects without worrying about order (obj + 5
or 5 + obj
both work).
What Happens If You Skip __radd__
?
Let’s remove __radd__
from the Money
class and run:
m1 = Money(10, 'USD')
print(5 + m1)
TypeError: unsupported operand type(s) for +: 'int' and 'Money'
+=
with __iadd__
class Money:
...
def __iadd__(self, other):
if self.currency != other.currency:
raise ValueError("Different currencies")
self.amount += other.amount
return self
m1 = Money(10, 'USD')
m1 += Money(2, 'USD')
print(m1) # 12 USD
+=
operator in Python uses __iadd__
, if defined.m1 += Money(2, 'USD')
is the same as calling m1.__iadd__(Money(2, 'USD'))
.__iadd__
:
__add__
.self.amount += ...
).self
, as Python expects that from __iadd__
.This method allows in-place addition. Useful if your object is mutable (which means it can change over time) and you want to avoid making new objects every time.
__radd__
and __iadd__
basically?__radd__
is called when the left object doesn’t know how to add, so Python tries the right one.__iadd__
handles the +=
operator.Operator | What Python Calls | Who Gets Called First |
---|---|---|
a + b |
a.__add__(b) |
Left-hand operand (a ) |
b + a |
b.__radd__(a) |
Right-hand operand (b ) |
a += b |
a.__iadd__(b) |
Left-hand, possibly mutates itself |
__radd__
If you don’t define __radd__
, expressions like 5 + obj
will fail, even if obj + 5
works.
__iadd__
If your object is mutable, +=
may change the original. If it’s immutable, +=
just creates a new one. Be explicit to avoid confusion.
Always return the same kind of object, or Python may behave weirdly (like returning int
from __add__
when it should be a Money
).
If a + b
works but b + a
doesn’t, you’ll confuse your future self (or other devs). Make sure your logic is symmetric when it should be.
Let’s try a more mathy example, a 2D vector.
class Vector2D:
def __init__(self, x, y):
self.x = x
self.y = y
def __add__(self, other):
return Vector2D(self.x + other.x, self.y + other.y)
def __sub__(self, other):
return Vector2D(self.x - other.x, self.y - other.y)
def __mul__(self, scalar):
return Vector2D(self.x * scalar, self.y * scalar)
def __rmul__(self, scalar):
return self.__mul__(scalar)
def __str__(self):
return f"({self.x}, {self.y})"
v1 = Vector2D(1, 2)
v2 = Vector2D(3, 4)
print(v1 + v2) # (4, 6)
print(v1 - v2) # (-2, -2)
print(v1 * 3) # (3, 6)
print(2 * v2) # (6, 8)
__add__
: adds the x and y coordinates of two vectors.
(1 + 3, 2 + 4)
→ (4, 6)
__sub__
: subtracts the x and y coordinates.
(1 - 3, 2 - 4)
→ (-2, -2)
__mul__
: multiplies both coordinates by a scalar.
(1 * 3, 2 * 3)
→ (3, 6)
__rmul__
: ensures scalar * vector
works (not just vector * scalar
) by redirecting to __mul__
.This class shows how you can fully overload arithmetic operators to model real-world entities like vectors, physics values, or even color channels.
__add__
, __sub__
, etc. unless you have a strong reason not to.NotImplemented
if you can’t handle a type. This lets Python try other methods like __radd__
.Arithmetic magic methods make your classes feel natural and powerful, almost like they’re part of the language. Whether you’re building a math library, a financial model, or even a game, these methods let you write clean, expressive code. But with great power comes great responsibility. Use them wisely, document clearly, and always test your logic from both sides.