【Python入門】演算子オーバーロードと特殊メソッドを完全解説!カスタムクラスでの演算子実装

 

Pythonの演算子オーバーロードと特殊メソッド(マジックメソッド)は、独自のクラスで標準的な演算子(+、-、==など)や組み込み関数(len()、str()など)を使えるようにする強力な機能です。この記事では、演算子オーバーロードの基本から実践的な使い方まで、初心者の方にも分かりやすく解説します。

演算子オーバーロードとは?

演算子オーバーロードとは、独自のクラスに対して、+、-、*、==などの演算子を使用できるようにする仕組みです。Pythonでは特殊メソッド(__add____sub__など)を定義することで実現します。

特殊メソッドの特徴

  • 前後にアンダースコア2個: __method__の形式
  • Pythonが自動的に呼び出し: 演算子使用時に内部で実行
  • 豊富な種類: 算術、比較、論理、型変換など様々

基本的な算術演算子のオーバーロード

1. 基本的な算術演算子

class Vector:
    def __init__(self, x, y):
        self.x = x
        self.y = y
    
    def __add__(self, other):
        """+ 演算子"""
        return Vector(self.x + other.x, self.y + other.y)
    
    def __sub__(self, other):
        """- 演算子"""
        return Vector(self.x - other.x, self.y - other.y)
    
    def __mul__(self, scalar):
        """* 演算子(スカラー倍)"""
        return Vector(self.x * scalar, self.y * scalar)
    
    def __str__(self):
        return f"Vector({self.x}, {self.y})"

# 使用例
v1 = Vector(3, 4)
v2 = Vector(1, 2)

v3 = v1 + v2  # __add__ が呼ばれる
v4 = v1 - v2  # __sub__ が呼ばれる
v5 = v1 * 2   # __mul__ が呼ばれる

print(v3)  # Vector(4, 6)
print(v4)  # Vector(2, 2)
print(v5)  # Vector(6, 8)

2. 完全な算術演算子セット

class Number:
    def __init__(self, value):
        self.value = value
    
    def __add__(self, other):
        return Number(self.value + other.value)
    
    def __sub__(self, other):
        return Number(self.value - other.value)
    
    def __mul__(self, other):
        return Number(self.value * other.value)
    
    def __truediv__(self, other):
        """/ 演算子(真の除算)"""
        if other.value == 0:
            raise ZeroDivisionError("ゼロで割ることはできません")
        return Number(self.value / other.value)
    
    def __floordiv__(self, other):
        """// 演算子(床除算)"""
        return Number(self.value // other.value)
    
    def __mod__(self, other):
        """% 演算子(剰余)"""
        return Number(self.value % other.value)
    
    def __pow__(self, other):
        """** 演算子(べき乗)"""
        return Number(self.value ** other.value)
    
    def __str__(self):
        return f"Number({self.value})"

n1 = Number(10)
n2 = Number(3)

print(n1 + n2)   # Number(13)
print(n1 / n2)   # Number(3.333...)
print(n1 // n2)  # Number(3)
print(n1 % n2)   # Number(1)
print(n1 ** n2)  # Number(1000)

比較演算子のオーバーロード

完全な比較演算子の実装

class Student:
    def __init__(self, name, score):
        self.name = name
        self.score = score
    
    def __eq__(self, other):
        """== 演算子"""
        return self.score == other.score
    
    def __ne__(self, other):
        """!= 演算子"""
        return self.score != other.score
    
    def __lt__(self, other):
        """< 演算子"""
        return self.score < other.score
    
    def __le__(self, other):
        """<= 演算子"""
        return self.score <= other.score
    
    def __gt__(self, other):
        """> 演算子"""
        return self.score > other.score
    
    def __ge__(self, other):
        """>= 演算子"""
        return self.score >= other.score
    
    def __str__(self):
        return f"{self.name}({self.score}点)"

# 使用例
student1 = Student("田中", 85)
student2 = Student("佐藤", 92)
student3 = Student("鈴木", 85)

print(student1 == student3)  # True(同じ点数)
print(student1 < student2)   # True(田中 < 佐藤)
print(student2 > student1)   # True(佐藤 > 田中)

# ソート機能も自動的に使用可能
students = [student1, student2, student3]
students.sort()  # 点数順にソート
for student in students:
    print(student)

実践的な演算子オーバーロード例

1. 分数クラス

from math import gcd

class Fraction:
    def __init__(self, numerator, denominator=1):
        if denominator == 0:
            raise ValueError("分母は0にできません")
        
        # 約分
        common = gcd(abs(numerator), abs(denominator))
        self.numerator = numerator // common
        self.denominator = denominator // common
        
        # 分母を正数にする
        if self.denominator < 0:
            self.numerator = -self.numerator
            self.denominator = -self.denominator
    
    def __add__(self, other):
        """分数の加算"""
        num = self.numerator * other.denominator + other.numerator * self.denominator
        den = self.denominator * other.denominator
        return Fraction(num, den)
    
    def __sub__(self, other):
        """分数の減算"""
        num = self.numerator * other.denominator - other.numerator * self.denominator
        den = self.denominator * other.denominator
        return Fraction(num, den)
    
    def __mul__(self, other):
        """分数の乗算"""
        num = self.numerator * other.numerator
        den = self.denominator * other.denominator
        return Fraction(num, den)
    
    def __truediv__(self, other):
        """分数の除算"""
        if other.numerator == 0:
            raise ZeroDivisionError("ゼロで割ることはできません")
        num = self.numerator * other.denominator
        den = self.denominator * other.numerator
        return Fraction(num, den)
    
    def __eq__(self, other):
        """分数の等価比較"""
        return (self.numerator == other.numerator and 
                self.denominator == other.denominator)
    
    def __str__(self):
        if self.denominator == 1:
            return str(self.numerator)
        return f"{self.numerator}/{self.denominator}"
    
    def __float__(self):
        """float型への変換"""
        return self.numerator / self.denominator

# 使用例
f1 = Fraction(1, 2)  # 1/2
f2 = Fraction(1, 3)  # 1/3
f3 = Fraction(2, 4)  # 2/4 → 1/2 に約分

print(f1 + f2)   # 5/6
print(f1 - f2)   # 1/6
print(f1 * f2)   # 1/6
print(f1 / f2)   # 3/2
print(f1 == f3)  # True(約分後で同じ)
print(float(f1)) # 0.5

2. 行列クラス

class Matrix:
    def __init__(self, data):
        self.data = [row[:] for row in data]  # 深いコピー
        self.rows = len(data)
        self.cols = len(data[0]) if data else 0
    
    def __add__(self, other):
        """行列の加算"""
        if self.rows != other.rows or self.cols != other.cols:
            raise ValueError("行列のサイズが異なります")
        
        result = []
        for i in range(self.rows):
            row = []
            for j in range(self.cols):
                row.append(self.data[i][j] + other.data[i][j])
            result.append(row)
        return Matrix(result)
    
    def __sub__(self, other):
        """行列の減算"""
        if self.rows != other.rows or self.cols != other.cols:
            raise ValueError("行列のサイズが異なります")
        
        result = []
        for i in range(self.rows):
            row = []
            for j in range(self.cols):
                row.append(self.data[i][j] - other.data[i][j])
            result.append(row)
        return Matrix(result)
    
    def __mul__(self, other):
        """行列の乗算またはスカラー倍"""
        if isinstance(other, (int, float)):
            # スカラー倍
            result = []
            for i in range(self.rows):
                row = []
                for j in range(self.cols):
                    row.append(self.data[i][j] * other)
                result.append(row)
            return Matrix(result)
        elif isinstance(other, Matrix):
            # 行列の乗算
            if self.cols != other.rows:
                raise ValueError("行列の乗算ができません")
            
            result = []
            for i in range(self.rows):
                row = []
                for j in range(other.cols):
                    sum_val = 0
                    for k in range(self.cols):
                        sum_val += self.data[i][k] * other.data[k][j]
                    row.append(sum_val)
                result.append(row)
            return Matrix(result)
    
    def __str__(self):
        lines = []
        for row in self.data:
            lines.append('[' + ', '.join(f'{x:4}' for x in row) + ']')
        return '[\n ' + '\n '.join(lines) + '\n]'

# 使用例
m1 = Matrix([[1, 2], [3, 4]])
m2 = Matrix([[5, 6], [7, 8]])

print("m1 + m2:")
print(m1 + m2)
# [   6,    8]
# [  10,   12]

print("m1 * 2:")
print(m1 * 2)
# [   2,    4]
# [   6,    8]

3. 金額クラス

class Money:
    def __init__(self, amount, currency="JPY"):
        self.amount = amount
        self.currency = currency
    
    def __add__(self, other):
        """金額の加算"""
        if self.currency != other.currency:
            raise ValueError(f"異なる通貨同士は計算できません: {self.currency} vs {other.currency}")
        return Money(self.amount + other.amount, self.currency)
    
    def __sub__(self, other):
        """金額の減算"""
        if self.currency != other.currency:
            raise ValueError(f"異なる通貨同士は計算できません: {self.currency} vs {other.currency}")
        return Money(self.amount - other.amount, self.currency)
    
    def __mul__(self, multiplier):
        """金額の乗算(数量との掛け算)"""
        if not isinstance(multiplier, (int, float)):
            raise TypeError("金額は数値とのみ乗算できます")
        return Money(self.amount * multiplier, self.currency)
    
    def __truediv__(self, divisor):
        """金額の除算"""
        if not isinstance(divisor, (int, float)):
            raise TypeError("金額は数値でのみ除算できます")
        if divisor == 0:
            raise ZeroDivisionError("ゼロで割ることはできません")
        return Money(self.amount / divisor, self.currency)
    
    def __eq__(self, other):
        """金額の等価比較"""
        return (self.amount == other.amount and 
                self.currency == other.currency)
    
    def __lt__(self, other):
        """金額の大小比較"""
        if self.currency != other.currency:
            raise ValueError("異なる通貨同士は比較できません")
        return self.amount < other.amount
    
    def __str__(self):
        return f"{self.amount:,.0f} {self.currency}"

# 使用例
price1 = Money(1000, "JPY")
price2 = Money(500, "JPY")
tax_rate = 1.1

total = price1 + price2
print(total)  # 1,500 JPY

total_with_tax = total * tax_rate
print(total_with_tax)  # 1,650 JPY

print(price1 > price2)  # True

特殊メソッドの種類と使い方

1. 文字列表現メソッド

class Book:
    def __init__(self, title, author, pages):
        self.title = title
        self.author = author
        self.pages = pages
    
    def __str__(self):
        """ユーザー向けの文字列表現"""
        return f"『{self.title}』- {self.author}"
    
    def __repr__(self):
        """開発者向けの文字列表現"""
        return f"Book('{self.title}', '{self.author}', {self.pages})"
    
    def __len__(self):
        """len()関数で呼ばれる"""
        return self.pages

book = Book("Python入門", "山田太郎", 300)

print(str(book))   # 『Python入門』- 山田太郎
print(repr(book))  # Book('Python入門', '山田太郎', 300)
print(len(book))   # 300

2. コンテナ操作メソッド

class CustomList:
    def __init__(self, items=None):
        self.items = items or []
    
    def __len__(self):
        """len()関数"""
        return len(self.items)
    
    def __getitem__(self, index):
        """list[index] でのアクセス"""
        return self.items[index]
    
    def __setitem__(self, index, value):
        """list[index] = value での設定"""
        self.items[index] = value
    
    def __contains__(self, item):
        """in 演算子"""
        return item in self.items
    
    def __iter__(self):
        """for文での反復"""
        return iter(self.items)
    
    def __str__(self):
        return f"CustomList({self.items})"

# 使用例
my_list = CustomList([1, 2, 3, 4, 5])

print(len(my_list))     # 5
print(my_list[2])       # 3
my_list[2] = 30
print(my_list[2])       # 30
print(3 in my_list)     # False(30に変更されたため)
print(30 in my_list)    # True

# for文で反復可能
for item in my_list:
    print(item, end=' ')  # 1 2 30 4 5

3. 型変換メソッド

class Temperature:
    def __init__(self, celsius):
        self.celsius = celsius
    
    def __int__(self):
        """int()関数での変換"""
        return int(self.celsius)
    
    def __float__(self):
        """float()関数での変換"""
        return float(self.celsius)
    
    def __bool__(self):
        """bool()関数での変換(絶対零度より高いかどうか)"""
        return self.celsius > -273.15
    
    def __str__(self):
        return f"{self.celsius}°C"

temp = Temperature(25.5)

print(int(temp))    # 25
print(float(temp))  # 25.5
print(bool(temp))   # True

# 条件判定でも使用可能
if temp:
    print("絶対零度より高い温度です")

特殊メソッド一覧表

算術演算子

演算子 メソッド 説明
+ __add__ 加算
__sub__ 減算
* __mul__ 乗算
/ __truediv__ 真の除算
// __floordiv__ 床除算
% __mod__ 剰余
** __pow__ べき乗

比較演算子

演算子 メソッド 説明
== __eq__ 等価
!= __ne__ 非等価
< __lt__ 未満
<= __le__ 以下
> __gt__ 超過
>= __ge__ 以上

その他の重要なメソッド

関数/演算子 メソッド 説明
str() __str__ 文字列表現
repr() __repr__ 開発者向け表現
len() __len__ 長さ
bool() __bool__ 真偽値変換
int() __int__ 整数変換
float() __float__ 浮動小数点変換

よくある間違いと注意点

1. 対称性の欠如

# ❌ 問題のある実装
class Number:
    def __init__(self, value):
        self.value = value
    
    def __add__(self, other):
        return Number(self.value + other.value)

n = Number(5)
# n + 3  # AttributeError: 'int' has no attribute 'value'

# ✅ 改善された実装
class Number:
    def __init__(self, value):
        self.value = value
    
    def __add__(self, other):
        if isinstance(other, Number):
            return Number(self.value + other.value)
        return Number(self.value + other)
    
    def __radd__(self, other):
        """右から加算される場合"""
        return Number(other + self.value)

2. 戻り値の型の一貫性

# ❌ 一貫性のない戻り値
class Vector:
    def __add__(self, other):
        return [self.x + other.x, self.y + other.y]  # リストを返す

# ✅ 一貫した戻り値
class Vector:
    def __add__(self, other):
        return Vector(self.x + other.x, self.y + other.y)  # 同じ型を返す

3. 不変性の考慮

# ✅ 不変オブジェクトとして実装
class ImmutablePoint:
    def __init__(self, x, y):
        self._x = x
        self._y = y
    
    @property
    def x(self):
        return self._x
    
    @property
    def y(self):
        return self._y
    
    def __add__(self, other):
        return ImmutablePoint(self.x + other.x, self.y + other.y)

まとめ

Python の演算子オーバーロードと特殊メソッドは、独自のクラスを組み込み型のように自然に使えるようにする強力な機能です。適切に実装することで、直感的で使いやすいAPIを提供できます。

重要なポイント

  • 特殊メソッドで演算子の動作を定義
  • 一貫性のあるインターフェースを提供
  • 型の対称性と不変性を考慮
  • 適切なエラーハンドリングを実装
  • ドキュメント化でメソッドの動作を明確化

まずは基本的な算術演算子から始めて、徐々に複雑な演算子オーバーロードを実装できるようになりましょう。実際にコードを書いて練習することで、演算子オーバーロードの概念がしっかりと身に付きます!

■プロンプトだけでオリジナルアプリを開発・公開してみた!!

■AI時代の第一歩!「AI駆動開発コース」はじめました!

テックジム東京本校で先行開始。

■テックジム東京本校

「武田塾」のプログラミング版といえば「テックジム」。
講義動画なし、教科書なし。「進捗管理とコーチング」で効率学習。
より早く、より安く、しかも対面型のプログラミングスクールです。

<短期講習>5日で5万円の「Pythonミニキャンプ」開催中。

<月1開催>放送作家による映像ディレクター養成講座

<オンライン無料>ゼロから始めるPython爆速講座