この単純な問題を考えてみましょう:
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などと宣言されているclass A(object)か、class A(B):どこBに新しいスタイルのクラスです。Python 3には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 !=演算子の非可換性の問題を修正するには、オペランドの型がサポートされていない場合に__eq__and __ne__メソッドがNotImplemented値を返す必要があります。ドキュメントには、定義NotImplementedとしての価値を:
数値メソッドと豊富な比較メソッドは、提供されたオペランドの演算を実装していない場合、この値を返すことがあります。(インタープリターは、オペレーターに応じて、反映された操作またはその他のフォールバックを試行します。)その真理値は真です。
この場合、演算子は比較演算を他のオペランドのリフレクトメソッドに委譲します。ドキュメント:定義のような方法を反映しました
これらのメソッドのスワップされた引数のバージョンはありません(左側の引数が操作をサポートしていないが、右側の引数はサポートしている場合に使用されます)。むしろ、__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
返す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
isオブジェクトIDと値の比較を区別する演算子があるので、これは明白だと思います。