==
カスタム クラスを作成する場合、および演算子を使用して同等性を許可することが重要になることがよくあります。Python では、それぞれおよび特殊メソッド!=
を実装することでこれが可能になります。これを行う最も簡単な方法は、次の方法です。__eq__
__ne__
class Foo:
def __init__(self, item):
self.item = item
def __eq__(self, other):
if isinstance(other, self.__class__):
return self.__dict__ == other.__dict__
else:
return False
def __ne__(self, other):
return not self.__eq__(other)
これをよりエレガントに行う方法をご存知ですか? 上記の比較方法を使用することの特定の欠点をご存知ですか__dict__
?
注: 少し説明を加えます。 と が未定義の場合__eq__
、__ne__
次の動作が発生します。
>>> a = Foo(1)
>>> b = Foo(1)
>>> a is b
False
>>> a == b
False
a == b
つまり、は と評価されます。これは、同一性のテスト (つまり、「 はと同じオブジェクトか?」)False
を実際に実行するためです。a is b
a
b
__eq__
と が定義されている場合__ne__
、次の動作が見られます (これが私たちが求めているものです)。
>>> a = Foo(1)
>>> b = Foo(1)
>>> a is b
False
>>> a == b
True
ベストアンサー1
次の簡単な問題を考えてみます。
class Number:
def __init__(self, number):
self.number = number
n1 = Number(1)
n2 = Number(1)
n1 == n2 # False -- oops
したがって、Python はデフォルトで比較演算にオブジェクト識別子を使用します。
id(n1) # 140400634555856
id(n2) # 140400634555920
関数をオーバーライドすると__eq__
問題は解決するようです:
def __eq__(self, other):
"""Overrides the default implementation"""
if isinstance(other, Number):
return self.number == other.number
return False
n1 == n2 # True
n1 != n2 # True in Python 2 -- oops, False in Python 3
Python 2では、__ne__
関数をオーバーライドすることも忘れないでください。ドキュメンテーション状態:
比較演算子間には暗黙の関係はありません。 が真であることは、が偽である
x==y
ことを意味しませんx!=y
。したがって、 を定義するときは__eq__()
、演算子が期待どおりに動作するように も定義する必要があります__ne__()
。
def __ne__(self, other):
"""Overrides the default implementation (unnecessary in Python 3)"""
return not self.__eq__(other)
n1 == n2 # True
n1 != n2 # False
Python 3では、これはもう必要ありません。ドキュメンテーション状態:
デフォルトでは、
__ne__()
は に委譲し__eq__()
、 でない限り、結果を反転しますNotImplemented
。比較演算子間には他の暗黙の関係はありません。たとえば、 が真であっても を(x<y or x==y)
意味するわけではありませんx<=y
。
しかし、それではすべての問題が解決するわけではありません。サブクラスを追加してみましょう。
class SubNumber(Number):
pass
n3 = SubNumber(1)
n1 == n3 # False for classic-style classes -- oops, True for new-style classes
n3 == n1 # True
n1 != n3 # True for classic-style classes -- oops, False for new-style classes
n3 != n1 # False
注: Python 2 には 2 種類のクラスがあります。
クラシックスタイル(または旧スタイルの) クラスで、を継承せず
object
、 として宣言されているものclass A:
、class A():
または が旧スタイルのクラスであるclass A(B):
もの。B
新しいスタイルクラスは を継承し
object
、 または として宣言されています。ここではは新しいスタイルのクラスです。Python 3 には、、class A(object)
またはとして宣言されている新しいスタイルのクラスのみがあります。class A(B):
B
class A:
class A(object):
class A(B):
クラシックスタイルのクラスの場合、比較演算は常に最初のオペランドのメソッドを呼び出しますが、新しいスタイルのクラスの場合、常にサブクラスのオペランドのメソッドを呼び出します。オペランドの順序に関係なく。
ここでは、Number
クラシック スタイルのクラスは次のようになります。
n1 == n3
通話n1.__eq__
;n3 == n1
通話n3.__eq__
;n1 != n3
通話n1.__ne__
;n3 != n1
呼び出しますn3.__ne__
。
そして、Number
新しいスタイルのクラスの場合:
- 両方
n1 == n3
とn3 == n1
呼び出しn3.__eq__
; - 両方
n1 != n3
をn3 != n1
呼び出しますn3.__ne__
。
Python 2のクラシックスタイルクラスの==
and演算子の非可換性の問題を修正するために、オペランドの型がサポートされていない場合は、andメソッドは値を返す必要があります。!=
__eq__
__ne__
NotImplemented
ドキュメンテーション値を次のように定義しますNotImplemented
。
数値メソッドと豊富な比較メソッドは、提供されたオペランドに対する演算を実装していない場合、この値を返すことがあります。(インタープリターは、演算子に応じて、反映された演算またはその他のフォールバックを試行します。) その真理値は true です。
この場合、演算子は比較演算を他のオペランドの反映されたメソッドに委任します。ドキュメンテーション反映されたメソッドを次のように定義します。
これらのメソッドには、引数を入れ替えたバージョンはありません (左の引数が操作をサポートしていないが、右の引数がサポートしている場合に使用します)。むしろ、
__lt__()
と は__gt__()
互いの反射であり、__le__()
は__ge__()
互いの反射であり、__eq__()
と は__ne__()
自身の反射です。
結果は次のようになります。
def __eq__(self, other):
"""Overrides the default implementation"""
if isinstance(other, Number):
return self.number == other.number
return NotImplemented
def __ne__(self, other):
"""Overrides the default implementation (unnecessary in Python 3)"""
x = self.__eq__(other)
if x is NotImplemented:
return NotImplemented
return not x
オペランドが関連のない型 (継承なし) である場合にand演算子の可換性NotImplemented
が求められる場合は、新しいスタイルのクラスでもの代わりに の値を返すFalse
のが適切です。==
!=
まだそこまで到達していませんか? まだです。固有の番号はいくつありますか?
len(set([n1, n2, n3])) # 3 -- oops
セットはオブジェクトのハッシュを使用し、デフォルトでは Python はオブジェクトの識別子のハッシュを返します。これをオーバーライドしてみましょう。
def __hash__(self):
"""Overrides the default implementation"""
return hash(tuple(sorted(self.__dict__.items())))
len(set([n1, n2, n3])) # 1
最終結果は次のようになります (検証のために最後にいくつかのアサーションを追加しました):
class Number:
def __init__(self, number):
self.number = number
def __eq__(self, other):
"""Overrides the default implementation"""
if isinstance(other, Number):
return self.number == other.number
return NotImplemented
def __ne__(self, other):
"""Overrides the default implementation (unnecessary in Python 3)"""
x = self.__eq__(other)
if x is not NotImplemented:
return not x
return NotImplemented
def __hash__(self):
"""Overrides the default implementation"""
return hash(tuple(sorted(self.__dict__.items())))
class SubNumber(Number):
pass
n1 = Number(1)
n2 = Number(1)
n3 = SubNumber(1)
n4 = SubNumber(4)
assert n1 == n2
assert n2 == n1
assert not n1 != n2
assert not n2 != n1
assert n1 == n3
assert n3 == n1
assert not n1 != n3
assert not n3 != n1
assert not n1 == n4
assert not n4 == n1
assert n1 != n4
assert n4 != n1
assert len(set([n1, n2, n3, ])) == 1
assert len(set([n1, n2, n3, n4])) == 2