__eq__()
演算子をオーバーライドしたいクラスがあります。 __ne__()
演算子もオーバーライドする必要があるのは理にかなっているようですが、__ne__
に基づいて__eq__
を実装するのは理にかなっていますか?
class A:
def __eq__(self, other):
return self.value == other.value
def __ne__(self, other):
return not self.__eq__(other)
または、Pythonがこれらの演算子を使用して、これは良い考えではない方法で欠けているものがありますか?
はい、それはまったく問題ありません。実際、 ドキュメント では、___ne__
_を定義するときに___eq__
_を定義するように促しています。
比較演算子間に暗黙の関係はありません。 _
x==y
_の真実は、_x!=y
_が偽であることを意味するものではありません。したがって、__eq__()
を定義するときは、演算子が期待どおりに動作するように__ne__()
も定義する必要があります。
多くの場合(このような場合)、___eq__
_の結果を否定するのと同じくらい簡単ですが、常にではありません。
ShadowRangerの__ne__
メソッドの実装は正しいものです(デフォルトのPython 3実装とまったく同じように動作するという意味で):
def __ne__(self, other):
result = self.__eq__(other)
if result is not NotImplemented:
return not result
return NotImplemented
アーロンホールのnot self == other
メソッドの実装__ne__
は、NotImplemented
(not NotImplemented
はFalse
)を返せないため、正しくありません。したがって、優先度のある__ne__
メソッドは、そうでない__ne__
メソッドにフォールバックできません。優先順位があります。 not self == other
は、以前は__ne__
メソッドのデフォルトのPython 3実装でしたが、バグであり、ShadowRangerが気づいたように、2015年1月のPython 3.4で修正されました( 問題#21408 )。
PythonのPython言語リファレンス3つの chapter IIIデータモデルの状態 :
object.__lt__(self, other)
object.__le__(self, other)
object.__eq__(self, other)
object.__ne__(self, other)
object.__gt__(self, other)
object.__ge__(self, other)
これらは、いわゆる「リッチ比較」方法です。演算子記号とメソッド名の対応は次のとおりです。
x<y
呼び出しx.__lt__(y)
、x<=y
呼び出しx.__le__(y)
、x==y
呼び出しx.__eq__(y)
、x!=y
呼び出しx.__ne__(y)
、x>y
呼び出しx.__gt__(y)
、およびx>=y
はx.__ge__(y)
を呼び出します。リッチ比較メソッドは、引数の特定のペアに対する操作を実装しない場合、シングルトン
NotImplemented
を返すことがあります。これらのメソッドの交換された引数バージョンはありません(左の引数は操作をサポートしないが、右の引数はサポートする場合に使用されます)。むしろ、
__lt__()
と__gt__()
はお互いの反射、__le__()
と__ge__()
はお互いの反射、__eq__()
と__ne__()
は独自の反射です。オペランドのタイプが異なり、右のオペランドのタイプが左のオペランドのタイプの直接または間接サブクラスである場合、右のオペランドのリフレクトされたメソッドが優先されます。それ以外の場合、左のオペランドのメソッドが優先されます。仮想サブクラスは考慮されません。
これをPythonコードに変換すると(operator_eq
には==
を、operator_ne
には!=
を、operator_lt
には<
を、operator_gt
には>
を、operator_le
には<=
に、operator_ge
には>=
を使用して)
def operator_eq(left, right):
if type(left) != type(right) and isinstance(right, type(left)):
result = right.__eq__(left)
if result is NotImplemented:
result = left.__eq__(right)
else:
result = left.__eq__(right)
if result is NotImplemented:
result = right.__eq__(left)
if result is NotImplemented:
result = left is right
return result
def operator_ne(left, right):
if type(left) != type(right) and isinstance(right, type(left)):
result = right.__ne__(left)
if result is NotImplemented:
result = left.__ne__(right)
else:
result = left.__ne__(right)
if result is NotImplemented:
result = right.__ne__(left)
if result is NotImplemented:
result = left is not right
return result
def operator_lt(left, right):
if type(left) != type(right) and isinstance(right, type(left)):
result = right.__gt__(left)
if result is NotImplemented:
result = left.__lt__(right)
else:
result = left.__lt__(right)
if result is NotImplemented:
result = right.__gt__(left)
if result is NotImplemented:
raise TypeError(f"'<' not supported between instances of '{type(left).__name__}' and '{type(right).__name__}'")
return result
def operator_gt(left, right):
if type(left) != type(right) and isinstance(right, type(left)):
result = right.__lt__(left)
if result is NotImplemented:
result = left.__gt__(right)
else:
result = left.__gt__(right)
if result is NotImplemented:
result = right.__lt__(left)
if result is NotImplemented:
raise TypeError(f"'>' not supported between instances of '{type(left).__name__}' and '{type(right).__name__}'")
return result
def operator_le(left, right):
if type(left) != type(right) and isinstance(right, type(left)):
result = right.__ge__(left)
if result is NotImplemented:
result = left.__le__(right)
else:
result = left.__le__(right)
if result is NotImplemented:
result = right.__ge__(left)
if result is NotImplemented:
raise TypeError(f"'<=' not supported between instances of '{type(left).__name__}' and '{type(right).__name__}'")
return result
def operator_ge(left, right):
if type(left) != type(right) and isinstance(right, type(left)):
result = right.__le__(left)
if result is NotImplemented:
result = left.__ge__(right)
else:
result = left.__ge__(right)
if result is NotImplemented:
result = right.__le__(left)
if result is NotImplemented:
raise TypeError(f"'>=' not supported between instances of '{type(left).__name__}' and '{type(right).__name__}'")
return result
ドキュメントには以下が追加されます。
デフォルトでは、
__ne__()
は__eq__()
に委任し、NotImplemented
でない限り結果を反転します。比較演算子間に他の暗黙の関係はありません。たとえば、(x<y or x==y)
の真実はx<=y
を意味しません。
比較メソッドのデフォルト実装(__eq__
、__ne__
、__lt__
、__gt__
、__le__
、および__ge__
)は、次のように指定できます。
def __eq__(self, other):
return NotImplemented
def __ne__(self, other):
result = self.__eq__(other)
if result is not NotImplemented:
return not result
return NotImplemented
def __lt__(self, other):
return NotImplemented
def __gt__(self, other):
return NotImplemented
def __le__(self, other):
return NotImplemented
def __ge__(self, other):
return NotImplemented
したがって、これは__ne__
メソッドの正しい実装です。また、__eq__
メソッドがNotImplemented
を返すとき、その逆__eq__
はFalse
(bool(NotImplemented)
はTrue
であるため)常にnot NotImplemented
メソッドの逆を返すとは限りません。 )目的のNotImplemented
の代わりに。
__ne__
の誤った実装アーロンホールが上記で示したように、not self.__eq__(other)
は__ne__
メソッドのデフォルトの実装ではありません。ただし、not self == other
.も、デフォルト実装の動作とnot self == other
実装の動作を2つのケースで比較することで、後者を以下に示します。
__eq__
メソッドはNotImplemented
;を返します。__eq__
メソッドは、NotImplemented
とは異なる値を返します。A.__ne__
メソッドがデフォルトの実装を使用し、A.__eq__
メソッドがNotImplemented
を返すとどうなるか見てみましょう。
class A:
pass
class B:
def __ne__(self, other):
return "B.__ne__"
assert (A() != B()) == "B.__ne__"
!=
はA.__ne__
を呼び出します。A.__ne__
はA.__eq__
を呼び出します。A.__eq__
はNotImplemented
を返します。!=
はB.__ne__
を呼び出します。B.__ne__
は"B.__ne__"
を返します。これは、A.__eq__
メソッドがNotImplemented
を返すと、A.__ne__
メソッドがB.__ne__
メソッドにフォールバックすることを示しています。
ここで、A.__ne__
メソッドがデフォルトの実装を使用し、A.__eq__
メソッドがNotImplemented
とは異なる値を返す場合に何が起こるかを見てみましょう。
class A:
def __eq__(self, other):
return True
class B:
def __ne__(self, other):
return "B.__ne__"
assert (A() != B()) is False
!=
はA.__ne__
を呼び出します。A.__ne__
はA.__eq__
を呼び出します。A.__eq__
はTrue
を返します。!=
はnot True
、つまりFalse
を返します。これは、この場合、A.__ne__
メソッドがA.__eq__
メソッドの逆を返すことを示しています。したがって、__ne__
メソッドは、ドキュメントで宣伝されているように動作します。
A.__ne__
メソッドのデフォルトの実装を上記の正しい実装でオーバーライドすると、同じ結果が得られます。
not self == other
の実装A.__ne__
メソッドのデフォルトの実装をnot self == other
実装でオーバーライドし、A.__eq__
メソッドがNotImplemented
を返すとどうなるか見てみましょう。
class A:
def __ne__(self, other):
return not self == other
class B:
def __ne__(self, other):
return "B.__ne__"
assert (A() != B()) is True
!=
はA.__ne__
を呼び出します。A.__ne__
は==
を呼び出します。==
はA.__eq__
を呼び出します。A.__eq__
はNotImplemented
を返します。==
はB.__eq__
を呼び出します。B.__eq__
はNotImplemented
を返します。==
はA() is B()
、つまりFalse
を返します。A.__ne__
はnot False
、つまりTrue
を返します。__ne__
メソッドのデフォルト実装は、True
ではなく、"B.__ne__"
を返しました。
次に、A.__ne__
メソッドのデフォルト実装をnot self == other
実装でオーバーライドし、A.__eq__
メソッドがNotImplemented
とは異なる値を返す場合に何が起こるかを見てみましょう。
class A:
def __eq__(self, other):
return True
def __ne__(self, other):
return not self == other
class B:
def __ne__(self, other):
return "B.__ne__"
assert (A() != B()) is False
!=
はA.__ne__
を呼び出します。A.__ne__
は==
を呼び出します。==
はA.__eq__
を呼び出します。A.__eq__
はTrue
を返します。A.__ne__
はnot True
、つまりFalse
を返します。__ne__
メソッドのデフォルト実装も、この場合False
を返しました。
この実装は、__ne__
メソッドがNotImplemented
を返すときに、__eq__
メソッドのデフォルト実装の動作の複製に失敗するため、正しくありません。