Pythonは初期のバージョンからオブジェクト同士を比較することを可能にしていました。オブジェクトにとって「大きい」または「等しい」とは何かを定義するために、特別な比較メソッド(eq, ne, lt, le, gt, ge)が作成されました。PEP 207(Python 2.1以降)の登場により、プログラマが比較時の動作を明示的に制御できるように、統一された比較プロトコルが実装されました。
標準タイプ(int, str)は「値」で比較されますが、ユーザークラスはデフォルトで「識別」によって比較されます(is)。比較メソッドを実装しない、または誤って実装した場合、オブジェクトの比較はコレクション、ソート、setやdictの使用時に予期しない動作をもたらし、時にはタイプエラーに繋がります。
クラス内で比較メソッドを明示的に定義する必要があります。一般的には、eq(等号)と__lt__(より小さい)を実装しますが、残りのメソッドはfunctools.total_orderingデコレーターを使って自動的に取得できます。
コード例:
from functools import total_ordering @total_ordering class Point: def __init__(self, x, y): self.x = x self.y = y def __eq__(self, other): if not isinstance(other, Point): return NotImplemented return self.x == other.x and self.y == other.y def __lt__(self, other): if not isinstance(other, Point): return NotImplemented return (self.x, self.y) < (other.x, other.y) # 使用例: p1 = Point(1, 2) p2 = Point(1, 3) print(p1 < p2) # True print(p1 == p2) # False
主な特徴:
比較メソッドを一つだけ(eq__または__lt)実装すれば、それで全てが機能するか?
いいえ。__eq__だけを実装した場合、等価性の正しい比較を保証しますが、<、>、<=、>=演算子は標準的に(通常オブジェクトの識別によって)動作します。完全な動作を実現するには、十分なセットのメソッドを実装するか、functools.total_orderingを利用する必要があります。
他の言語のように、Pythonにcompare(x, y)が存在しない理由は?
Pythonは特殊な「マジカル」メソッドのアイデアを採用し、cmp関数はPython 3以降削除されました。オブジェクトの一般比較にはリッチ比較メソッド(lt, __eq__など)を使用し、ソートにはsort/sorted関数のkeyパラメータを使用します。
必要なメソッドが実装されていないタイプとオブジェクトを比較した場合、どうなるか?
__eq__や他のメソッドでサポートされていないタイプに対してNotImplementedを返すと、Pythonは自動的に第二オペランドの逆メソッドを試みるか、Falseを返します。このようなケースを処理しないと、TypeErrorや全く間違った動作が発生する可能性があります。
コード例:
class Foo: def __eq__(self, other): return NotImplemented class Bar: pass print(Foo() == Bar()) # False(Bar.__eq__経由で試行)
開発者がUserクラスを作成し、__hash__なしで__eq__のみを実装したため、ユーザーはemailで等しいと見なされました。その後、Userオブジェクトをsetや辞書のキーとして使用しようとしました。
メリット:
デメリット:
プログラマーが@total_orderingを使用し、タイプチェック付きの__eq__と__lt__を実装し、かつ変更不可のクラスには__hash__を追加します。オブジェクトは正しく比較され、ソートされ、辞書のキーとしても機能します。
メリット:
デメリット: