Operator Overloading in Python: Defining Arithmetic Operations (+, -, *, /) in Custom Classes

In Python, just as you can use operators like + and - with numeric types (int and float), you can also define the behavior of these operators for classes (objects) you create yourself. This is called “Operator Overloading.”

For example, when dealing with vector calculations, matrix calculations, or custom currency classes, this allows for intuitive coding. This article explains how to implement special methods (magic methods) corresponding to arithmetic operations, with concrete code examples.


目次

List of Special Methods for Arithmetic Operators

The special methods corresponding to each operator are as follows. By defining (overriding) these within a class, you can determine the behavior when that operator is used.

OperatorSpecial MethodMeaning
+__add__Addition
-__sub__Subtraction
*__mul__Multiplication
/__truediv__True Division (Standard division)
//__floordiv__Floor Division (Integer division)
%__mod__Modulo (Remainder of division)
**__pow__Exponentiation (Power)

Implementation Example: 2D Vector Class

As an example, let’s create a Vector class representing a mathematical vector (having x and y components). We will implement addition and subtraction between vectors, and multiplication and division with scalars (numbers).

class Vector:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __repr__(self):
        # Display clearly for debugging
        return f"Vector({self.x}, {self.y})"

    # --- Addition (+) ---
    def __add__(self, other):
        # Addition between Vectors
        if isinstance(other, Vector):
            return Vector(self.x + other.x, self.y + other.y)
        return NotImplemented

    # --- Subtraction (-) ---
    def __sub__(self, other):
        # Subtraction between Vectors
        if isinstance(other, Vector):
            return Vector(self.x - other.x, self.y - other.y)
        return NotImplemented

    # --- Multiplication (*) ---
    def __mul__(self, scalar):
        # Vector * Scalar (Number)
        if isinstance(scalar, (int, float)):
            return Vector(self.x * scalar, self.y * scalar)
        return NotImplemented

    # --- True Division (/) ---
    def __truediv__(self, scalar):
        # Vector / Scalar (Number)
        if isinstance(scalar, (int, float)):
            if scalar == 0:
                raise ValueError("Division by zero is not allowed.")
            return Vector(self.x / scalar, self.y / scalar)
        return NotImplemented

    # --- Floor Division (//) ---
    def __floordiv__(self, scalar):
        # Vector // Scalar (Number)
        if isinstance(scalar, (int, float)):
            if scalar == 0:
                raise ValueError("Division by zero is not allowed.")
            return Vector(self.x // scalar, self.y // scalar)
        return NotImplemented

    # --- Modulo (%) ---
    def __mod__(self, scalar):
        # Vector % Scalar (Number)
        if isinstance(scalar, (int, float)):
            if scalar == 0:
                raise ValueError("Division by zero is not allowed.")
            return Vector(self.x % scalar, self.y % scalar)
        return NotImplemented

    # --- Exponentiation (**) ---
    def __pow__(self, exponent):
        # Vector ** Exponent
        if isinstance(exponent, (int, float)):
            return Vector(self.x ** exponent, self.y ** exponent)
        return NotImplemented

# --- Verification ---

v1 = Vector(10, 20)
v2 = Vector(3, 4)

print(f"v1: {v1}")
print(f"v2: {v2}")
print("-" * 20)

# Addition and Subtraction
print(f"v1 + v2 = {v1 + v2}")
print(f"v1 - v2 = {v1 - v2}")

# Multiplication (Scalar)
print(f"v1 * 2  = {v1 * 2}")

# Division
print(f"v1 / 4  = {v1 / 4}")   # Result includes float
print(f"v1 // 3 = {v1 // 3}")  # Result is int (floored)

# Modulo and Exponentiation
print(f"v1 % 3  = {v1 % 3}")
print(f"v2 ** 2 = {v2 ** 2}")

Output:

v1: Vector(10, 20)
v2: Vector(3, 4)
--------------------
v1 + v2 = Vector(13, 24)
v1 - v2 = Vector(7, 16)
v1 * 2  = Vector(20, 40)
v1 / 4  = Vector(2.5, 5.0)
v1 // 3 = Vector(3, 6)
v1 % 3  = Vector(1, 2)
v2 ** 2 = Vector(9, 16)

Implementation Points

1. Type Checking (isinstance)

It is important to check if the operand is of the expected type. For example, in __add__, calculations are performed only if the other operand (other) is also a Vector class. In __mul__, it handles cases where the operand is a number (int or float).

2. Returning NotImplemented

If the types do not match or the operation is undefined, Python convention dictates returning NotImplemented instead of raising an error (TypeError). This allows the Python interpreter to determine “calculation is not possible with this class” and leaves room to try the reverse operation (e.g., other.__radd__) on the object on the right side of the operator.

3. Returning a New Instance

Generally, the result of an operation should return a new instance rather than modifying itself (self) (immutable behavior). This prevents side effects where v1‘s value changes when executing v3 = v1 + v2.

4. Handling Zero Division

For division-related operations (/, //, %), you can enhance safety by writing code to raise a ValueError or ZeroDivisionError when the denominator is 0.


Summary

  • By defining special methods like __add__ and __sub__, you can use + and - with custom classes.
  • Check the type of the other operand; if calculable, return a new instance, otherwise return NotImplemented.
  • This enables intuitive coding and improves the usability of the class.
よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

私が勉強したこと、実践したこと、してることを書いているブログです。
主に資産運用について書いていたのですが、
最近はプログラミングに興味があるので、今はそればっかりです。

目次