この単純な問題を考えてみましょう:
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 種類のクラスがあります。
から継承せず、 として宣言されているクラシック スタイル(または古いスタイル) クラス、またははクラシックスタイルのクラスです。objectclass A:class A():class A(B):B
から継承し、新しいスタイルのobjectクラスとして宣言されているclass A(object)新しいスタイルのクラス。Python 3 には、 、またはとして宣言された新しいスタイルのクラスしかありません。class A(B):Bclass 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__NotImplementedNotImplemented
数値メソッドと豊富な比較メソッドは、提供されたオペランドの演算を実装していない場合、この値を返すことがあります。(その後、インタープリターは、オペレーターに応じて、反映された操作またはその他のフォールバックを試みます。) その真偽値は 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