Pythonでは、数値型(intやfloat)同士で + や - などの演算子が使えるのと同様に、自分で作成したクラス(オブジェクト)に対しても、これらの演算子の動作を定義することができます。これを「演算子のオーバーロード」と呼びます。
例えば、ベクトル計算や行列計算、あるいは独自の通貨クラスなどを扱う際に、直感的な記述が可能になります。
この記事では、算術演算に対応する特殊メソッド(マジックメソッド)の実装方法について、具体的なコード例を交えて解説します。
算術演算子に対応する特殊メソッド一覧
各演算子に対応する特殊メソッドは以下の通りです。これらをクラス内で定義(オーバーライド)することで、その演算子が使用されたときの動作を決定できます。
| 演算子 | 特殊メソッド | 意味 |
+ | __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 の場合に ValueError や ZeroDivisionError を発生させるように記述することで、安全性を高めることができます。
まとめ
__add__や__sub__などの特殊メソッドを定義することで、自作クラスで+や-を使用できるようになります。- 相手の型を確認し、計算可能なら新しいインスタンスを返し、不可能なら
NotImplementedを返します。 - 直感的なコード記述が可能になり、クラスの利便性が向上します。
