__sub__ / __rsub__ / __isub__

__sub__(self, other)
Returns: Any · Updated March 27, 2026 · Dunder Methods
dunder operator subtraction arithmetic

__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

MethodExpressionCalled forReturn
__sub__a - bleft operand is a’s typenew object
__rsub__a - ba.__sub__(b) returns NotImplementednew object
__isub__a -= baugmented assignmentself

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

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