Pythonの演算子オーバーロード:自作クラスで四則演算(+, -, *, /)を定義する方法

Pythonでは、数値型(intfloat)同士で +- などの演算子が使えるのと同様に、自分で作成したクラス(オブジェクト)に対しても、これらの演算子の動作を定義することができます。これを「演算子のオーバーロード」と呼びます。

例えば、ベクトル計算や行列計算、あるいは独自の通貨クラスなどを扱う際に、直感的な記述が可能になります。

この記事では、算術演算に対応する特殊メソッド(マジックメソッド)の実装方法について、具体的なコード例を交えて解説します。

目次

算術演算子に対応する特殊メソッド一覧

各演算子に対応する特殊メソッドは以下の通りです。これらをクラス内で定義(オーバーライド)することで、その演算子が使用されたときの動作を決定できます。

演算子特殊メソッド意味
+__add__加算
-__sub__減算
*__mul__乗算
/__truediv__真除算(通常の割り算)
//__floordiv__切り捨て除算(整数除算)
%__mod__剰余(割り算の余り)
**__pow__べき乗

実装例:2次元ベクトルクラス

例として、数学的なベクトル(x, y 成分を持つ)を表す Vector クラスを作成します。

このクラスに対し、ベクトル同士の加減算や、スカラー(数値)との乗除算を実装します。

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

    def __repr__(self):
        # デバッグ用にわかりやすく表示
        return f"Vector({self.x}, {self.y})"

    # --- 加算 (+) ---
    def __add__(self, other):
        # Vector同士の足し算
        if isinstance(other, Vector):
            return Vector(self.x + other.x, self.y + other.y)
        return NotImplemented

    # --- 減算 (-) ---
    def __sub__(self, other):
        # Vector同士の引き算
        if isinstance(other, Vector):
            return Vector(self.x - other.x, self.y - other.y)
        return NotImplemented

    # --- 乗算 (*) ---
    def __mul__(self, scalar):
        # Vector * スカラー(数値)
        if isinstance(scalar, (int, float)):
            return Vector(self.x * scalar, self.y * scalar)
        return NotImplemented

    # --- 真除算 (/) ---
    def __truediv__(self, scalar):
        # Vector / スカラー(数値)
        if isinstance(scalar, (int, float)):
            if scalar == 0:
                raise ValueError("ゼロ除算はできません。")
            return Vector(self.x / scalar, self.y / scalar)
        return NotImplemented

    # --- 切り捨て除算 (//) ---
    def __floordiv__(self, scalar):
        # Vector // スカラー(数値)
        if isinstance(scalar, (int, float)):
            if scalar == 0:
                raise ValueError("ゼロ除算はできません。")
            return Vector(self.x // scalar, self.y // scalar)
        return NotImplemented

    # --- 剰余 (%) ---
    def __mod__(self, scalar):
        # Vector % スカラー(数値)
        if isinstance(scalar, (int, float)):
            if scalar == 0:
                raise ValueError("ゼロ除算はできません。")
            return Vector(self.x % scalar, self.y % scalar)
        return NotImplemented

    # --- べき乗 (**) ---
    def __pow__(self, exponent):
        # Vector ** 指数
        if isinstance(exponent, (int, float)):
            return Vector(self.x ** exponent, self.y ** exponent)
        return NotImplemented

# --- 動作確認 ---

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

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

# 加算と減算
print(f"v1 + v2 = {v1 + v2}")
print(f"v1 - v2 = {v1 - v2}")

# 乗算(スカラー倍)
print(f"v1 * 2  = {v1 * 2}")

# 除算
print(f"v1 / 4  = {v1 / 4}")   # 結果はfloatを含む
print(f"v1 // 3 = {v1 // 3}")  # 結果はint(切り捨て)

# 剰余とべき乗
print(f"v1 % 3  = {v1 % 3}")
print(f"v2 ** 2 = {v2 ** 2}")

実行結果:

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)

実装のポイント

1. 型チェック (isinstance)

演算対象が期待する型であるかを確認することが重要です。

例えば __add__ では、相手(other)が同じ Vector クラスである場合にのみ計算を行います。__mul__ では、相手が数値(int または float)である場合に対応しています。

2. NotImplemented の返却

型が一致しない場合や、定義されていない演算の場合は、エラー(TypeError)を投げるのではなく、return NotImplemented とするのがPythonの作法です。

これにより、Pythonインタプリタは「このクラスでは計算できない」と判断し、演算子の右側のオブジェクトに対して逆演算(例: other.__radd__)を試みる余地を残します。

3. 新しいインスタンスの返却

演算の結果は、自分自身(self)を書き換えるのではなく、新しいインスタンスを作成して返すのが一般的です(イミュータブルな振る舞い)。これにより v3 = v1 + v2 としたときに、v1 の値が変わってしまう副作用を防げます。

4. ゼロ除算のハンドリング

除算系の演算(/, //, %)では、分母が 0 の場合に ValueErrorZeroDivisionError を発生させるように記述することで、安全性を高めることができます。

まとめ

  • __add____sub__ などの特殊メソッドを定義することで、自作クラスで +- を使用できるようになります。
  • 相手の型を確認し、計算可能なら新しいインスタンスを返し、不可能なら NotImplemented を返します。
  • 直感的なコード記述が可能になり、クラスの利便性が向上します。
よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

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

目次