__sub__ / __rsub__ / __isub__
__sub__(self, other) Any · Updated March 27, 2026 · Dunder Methods __sub__ — Binary Subtraction
__sub__ implements the expression self - other. Python calls it when your object is the left operand in a subtraction.
class Vector:
def __init__(self, x, y):
self.x, self.y = x, y
def __sub__(self, other):
return Vector(self.x - other.x, self.y - other.y)
def __repr__(self):
return f"Vector({self.x}, {self.y})"
v1 = Vector(8, 5)
v2 = Vector(3, 2)
v3 = v1 - v2
print(v3) # Vector(5, 3)
If __sub__ returns NotImplemented, Python tries the right-hand operand’s __rsub__. If the method is absent or returns anything other than an object, that becomes the result of the expression.
__rsub__ — Reversed Subtraction
__rsub__ implements other - self. Python calls it when the left operand’s __sub__ returns NotImplemented and the left operand is a type Python can’t introspect further (like a C extension type).
The reversed form matters because subtraction is not commutative — a - b and b - a give different results.
class PercentOf:
def __init__(self, value):
self.value = value
def __rsub__(self, other):
# Called for: other - self
return (other - self.value) / self.value * 100
p = PercentOf(4)
result = 20 - p # calls p.__rsub__(20)
print(f"{result:.1f}%") # 400.0%
Without __rsub__, Python would not know how to subtract a PercentOf from 20.
When __rsub__ is called
Python checks __rsub__ only after the left operand’s __sub__ returns NotImplemented. Built-in types like int and list often return NotImplemented for unknown types, but not always — the exact fallback behavior depends on the C-level implementation. This is a common source of confusion when mixing custom objects with builtins.
Non-commutativity in practice
Because a - b and b - a differ, Python must choose which method to call based on operand order. If the left type doesn’t recognize the right type, the right type gets a chance to handle the operation through __rsub__.
class A:
def __init__(self, v):
self.v = v
def __sub__(self, other):
return f"A({self.v} - {other})"
def __rsub__(self, other):
return f"A({other} - {self.v})"
a = A(5)
print(a - 3) # A(5 - 3)
print(3 - a) # A(3 - 5)
__isub__ — In-Place Subtraction
__isub__ implements self -= other. Python calls it for augmented assignment. The method must mutate self and return self — forgetting the return statement is the most common implementation error.
class Account:
def __init__(self, balance):
self.balance = balance
def __isub__(self, amount):
self.balance -= amount
return self # must return self
def __repr__(self):
return f"Account(balance={self.balance})"
acc = Account(1000)
acc -= 250
print(acc) # Account(balance=750)
If __isub__ does not return self, the variable gets rebound to whatever was returned instead:
class Broken:
def __isub__(self, other):
self.value -= other
return 42 # wrong — should return self
b = Broken()
b -= 1 # b is now 42
Fallback when __isub__ is absent
If a class defines __sub__ but not __isub__, Python falls back to __sub__ and assigns the result back to the variable. The object identity changes because a new object is created:
class NoInPlace:
def __init__(self, value):
self.value = value
def __sub__(self, other):
return NoInPlace(self.value - other)
# no __isub__
obj = NoInPlace(10)
id_before = id(obj)
obj -= 3
id_after = id(obj)
print(id_before == id_after) # False — a new object replaced the old one
This is inefficient compared to in-place mutation, and it breaks code that holds references to the original object.
Return Values and Error Handling
All three methods should return an object (or NotImplemented). Returning NotImplemented signals that the operation is not supported, and Python will try the other method if applicable.
class Strict:
def __sub__(self, other):
if not isinstance(other, Strict):
return NotImplemented
return Strict(other.value - self.value)
Returning anything other than an object or NotImplemented leads to hard-to-debug type errors at the call site.
Summary
| Method | Expression | Called for | Return |
|---|---|---|---|
__sub__ | a - b | left operand is a’s type | new object |
__rsub__ | a - b | a.__sub__(b) returns NotImplemented | new object |
__isub__ | a -= b | augmented assignment | self |
The critical difference between __sub__ and __isub__: __sub__ returns a new object, while __isub__ must mutate and return self. For immutable types like numbers this distinction doesn’t matter much — both return a new value. For mutable objects like collections or game state, in-place subtraction avoids unnecessary allocations.
See Also
- add / radd / iadd — the addition counterparts
- dunder-mul — multiplication operators
- dunder-eq — equality comparison
Written
- File: sites/pyguides/src/content/reference/dunder-methods/dunder-sub.md
- Words: ~530
- Read time: 2 min
- Topics covered:
__sub__,__rsub__,__isub__, return values,NotImplemented, fallback behavior, object identity, non-commutativity - Verified via: docs.python.org/3/reference/datamodel.html#emulating-numeric-types
- Unverified items: none