Pythonの比較演算子オーバーロード:自作クラスの大小関係(<, <=, ==, etc.)を定義する方法

数値や文字列同士で a < ba == b といった比較ができるように、自作のクラス(オブジェクト)同士でも大小関係や等価性を定義したい場合があります。

例えば、バージョン番号を管理するクラスで「バージョン1.2は1.5より古い(小さい)」と判定したり、日付範囲クラスで「期間が重なっているか」を判定したりするケースです。

この記事では、比較演算子に対応する特殊メソッドの実装方法と、実装を楽にする functools.total_ordering デコレータについて解説します。

目次

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

各比較演算子に対応する特殊メソッドは以下の通りです。

演算子特殊メソッド英語(意味)
==__eq__Equal (等しい)
!=__ne__Not Equal (等しくない)
<__lt__Less Than (より小さい)
<=__le__Less than or Equal (以下)
>__gt__Greater Than (より大きい)
>=__ge__Greater than or Equal (以上)

これらをすべて実装すれば完全な比較が可能になりますが、実はすべてを書く必要はありません。

実装例:ソフトウェアのバージョン比較

例として、ソフトウェアのバージョン(メジャー.マイナー.パッチ)を管理する Version クラスを作成します。

1.2.0 と 1.10.0 を文字列として比較すると “1.2.0” > “1.10.0” となってしまいますが、クラスとして正しく実装すれば数値的な大小関係で比較できます。

functools.total_ordering による効率化

Pythonには便利なデコレータ @total_ordering が用意されています(functools モジュール)。

これを使用すると、__eq__ と、大小比較のいずれか一つ(例えば __lt__)を定義するだけで、残りの演算子(<=, >, >=)をPythonが自動的に補完してくれます。

from functools import total_ordering

@total_ordering
class Version:
    def __init__(self, major, minor, patch=0):
        self.major = major
        self.minor = minor
        self.patch = patch

    def __repr__(self):
        return f"v{self.major}.{self.minor}.{self.patch}"

    def _to_tuple(self):
        """比較用のタプルを返すヘルパーメソッド"""
        return (self.major, self.minor, self.patch)

    # --- 必須: 等価判定 (==) ---
    def __eq__(self, other):
        if not isinstance(other, Version):
            return NotImplemented
        return self._to_tuple() == other._to_tuple()

    # --- 必須: いずれか一つの大小比較 (ここでは < ) ---
    def __lt__(self, other):
        if not isinstance(other, Version):
            return NotImplemented
        # タプル同士の比較は、先頭の要素から順に比較される
        # (1, 2, 0) < (1, 10, 0) -> True
        return self._to_tuple() < other._to_tuple()

# --- 動作確認 ---

v1 = Version(1, 2, 5)
v2 = Version(1, 10, 0)
v3 = Version(1, 2, 5)

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

# 定義した __eq__ と __lt__
print(f"v1 == v3 : {v1 == v3}")  # True
print(f"v1 < v2  : {v1 < v2}")   # True (1.2.5 < 1.10.0)

# @total_ordering が自動生成してくれた演算子
print(f"v1 != v2 : {v1 != v2}")  # True
print(f"v2 > v1  : {v2 > v1}")   # True
print(f"v1 <= v3 : {v1 <= v3}")  # True

実行結果:

v1: v1.2.5
v2: v1.10.0
v3: v1.2.5
--------------------
v1 == v3 : True
v1 < v2  : True
v1 != v2 : True
v2 > v1  : True
v1 <= v3 : True

解説

  1. _to_tuple: 比較ロジックを簡単にするため、属性をタプル (major, minor, patch) にまとめるメソッドを用意しました。Pythonのタプルは自動的に先頭の要素から順に比較を行ってくれるため、これを利用します。
  2. __eq__: 等しいかどうかを判定します。
  3. __lt__: 「より小さい」かどうかを判定します。
  4. @total_ordering: 上記2つを元に、残りの __le__, __gt__, __ge__, __ne__ を自動的に生成します。

これにより、最小限のコード量で直感的なオブジェクト比較が可能になります。

別の例:面積による図形の比較

もし単一の数値(スカラー値)で大小が決まるようなオブジェクトであれば、実装はさらに単純になります。

例として、半径を持つ Circle(円)クラスを作成し、面積の大きさで比較できるようにします。

import math

@total_ordering
class Circle:
    def __init__(self, radius):
        self.radius = radius

    @property
    def area(self):
        """面積を計算するプロパティ"""
        return self.radius * self.radius * math.pi

    def __repr__(self):
        return f"Circle(r={self.radius})"

    def __eq__(self, other):
        if not isinstance(other, Circle):
            return NotImplemented
        return self.radius == other.radius

    def __lt__(self, other):
        if not isinstance(other, Circle):
            return NotImplemented
        # 半径(または面積)の大小で比較
        return self.radius < other.radius

# --- 動作確認 ---
c1 = Circle(5)
c2 = Circle(10)

# sort() 関数なども使えるようになる
circles = [c2, Circle(1), c1]
sorted_circles = sorted(circles)

print(f"ソート前: {circles}")
print(f"ソート後: {sorted_circles}")

実行結果:

ソート前: [Circle(r=10), Circle(r=1), Circle(r=5)]
ソート後: [Circle(r=1), Circle(r=5), Circle(r=10)]

比較演算子を実装することで、リストの sorted() 関数や sort() メソッドがそのまま利用できるようになるのも大きなメリットです。

まとめ

  • 自作クラスで比較演算子(==, <, >など)を使うには、__eq__, __lt__ などの特殊メソッドを実装します。
  • functools.total_ordering デコレータを使うと、__eq____lt__(または他の一つ)を定義するだけで、すべての比較演算子を使えるようになります。
  • これにより、オブジェクトのソートや大小判定が直感的に行えるようになります。
よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

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

目次