python、__eq__に基づいて__ne __()演算子を実装する必要がありますか?
-
08-10-2019 - |
質問
私はオーバーライドしたいクラスを持っています __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__
, 、 しかしいつもではない。
他のヒント
Python、実装する必要があります
__ne__()
に基づいてオペレーター__eq__
?
短い答え:いいえ。使用 ==
の代わりに __eq__
Python 3で、 !=
の否定です ==
デフォルトでは、あなたは書く必要さえありません __ne__
, 、そして、ドキュメントはもはや書くことについて意見を述べていません。
一般的に言えば、Python 3のみのコードの場合、Builtinオブジェクトの場合、親の実装を覆い隠す必要がない限り、それを記述しないでください。
つまり、覚えておいてください レイモンド・ヘッティンガーのコメント:
__ne__
メソッドはから自動的に続きます__eq__
場合にのみ__ne__
スーパークラスではまだ定義されていません。したがって、ビルトインから相続している場合は、両方をオーバーライドするのが最善です。
Python 2で動作するコードが必要な場合は、Python 2の推奨に従ってください。Python3で機能します。
Python 2では、Python自体は別の操作に関して自動的に操作を実装していません。したがって、次のように定義する必要があります。 __ne__
の面では ==
の代わりに __eq__
。例えば
class A(object):
def __eq__(self, other):
return self.value == other.value
def __ne__(self, other):
return not self == other # NOT `return not self.__eq__(other)`
その証拠を参照してください
- 実装
__ne__()
に基づいてオペレーター__eq__
と - 実装していません
__ne__
Python 2ではまったく
以下のデモで誤った動作を提供します。
長い答え
ドキュメンテーション Python 2の場合:
比較演算子間に暗黙の関係はありません。の真実
x==y
それを意味しませんx!=y
偽です。したがって、定義するとき__eq__()
, 、定義する必要があります__ne__()
オペレーターが予想どおりに動作するように。
それは、私たちが定義する場合を意味します __ne__
の逆の観点から __eq__
, 、一貫した動作を得ることができます。
ドキュメントのこのセクションは更新されました Python 3:
デフォルトでは、
__ne__()
代表者__eq__()
そうでない限り、結果を反転しますNotImplemented
.
とで 「新しいもの」セクション, 、この動作が変わったことがわかります:
!=
今、反対を返します==
, 、 そうでもなければ==
戻り値NotImplemented
.
実装のため __ne__
, 、使用することを好みます ==
オペレーター を使用する代わりに __eq__
IFを直接メソッド self.__eq__(other)
サブクラスのリターンの NotImplemented
チェックされたタイプの場合、Pythonは適切にチェックします other.__eq__(self)
ドキュメントから:
NotImplemented
物体
このタイプには単一の値があります。この値の単一のオブジェクトがあります。このオブジェクトは、内蔵名からアクセスされます
NotImplemented
. 。数値的方法とリッチ比較方法は、提供されたオペランドの操作を実装しない場合、この値を返す場合があります。 (通訳者は、オペレーターに応じて反射操作または他のフォールバックを試します。)その真理値は真です。
豊富な比較演算子が与えられた場合、それらが同じタイプでない場合、Pythonは other
サブタイプであり、そのオペレーターが定義されている場合、 other
最初の方法(逆の方法 <
, <=
, >=
と >
)。もしも NotImplemented
返されます、 それから 反対の方法を使用します。 (します いいえ 同じ方法を2回確認してください。) ==
オペレーターでは、このロジックを実行できます。
期待
意味的には、実装する必要があります __ne__
あなたのクラスのユーザーは、次の機能がaのすべてのインスタンスと同等であることを期待するため、平等のチェックに関して::
def negation_of_equals(inst1, inst2):
"""always should return same as not_equals(inst1, inst2)"""
return not inst1 == inst2
def not_equals(inst1, inst2):
"""always should return same as negation_of_equals(inst1, inst2)"""
return inst1 != inst2
つまり、上記の関数の両方が必要です いつも 同じ結果を返します。しかし、これはプログラマーに依存しています。
定義するときの予期しない行動のデモンストレーション __ne__
に基づく __eq__
:
最初にセットアップ:
class BaseEquatable(object):
def __init__(self, x):
self.x = x
def __eq__(self, other):
return isinstance(other, BaseEquatable) and self.x == other.x
class ComparableWrong(BaseEquatable):
def __ne__(self, other):
return not self.__eq__(other)
class ComparableRight(BaseEquatable):
def __ne__(self, other):
return not self == other
class EqMixin(object):
def __eq__(self, other):
"""override Base __eq__ & bounce to other for __eq__, e.g.
if issubclass(type(self), type(other)): # True in this example
"""
return NotImplemented
class ChildComparableWrong(EqMixin, ComparableWrong):
"""__ne__ the wrong way (__eq__ directly)"""
class ChildComparableRight(EqMixin, ComparableRight):
"""__ne__ the right way (uses ==)"""
class ChildComparablePy3(EqMixin, BaseEquatable):
"""No __ne__, only right in Python 3."""
非等価インスタンスのインスタンス:
right1, right2 = ComparableRight(1), ChildComparableRight(2)
wrong1, wrong2 = ComparableWrong(1), ChildComparableWrong(2)
right_py3_1, right_py3_2 = BaseEquatable(1), ChildComparablePy3(2)
予想される行動:
(注:以下のそれぞれの2秒ごとのアサーションは同等であり、したがって、その前のものに対して論理的に冗長性がありますが、私はそれらを含めてそれを示しています 一方が他方のサブクラスである場合、注文は関係ありません。)
これらのインスタンスがあります __ne__
で実装 ==
:
assert not right1 == right2
assert not right2 == right1
assert right1 != right2
assert right2 != right1
Python 3でテストするこれらのインスタンスも正しく機能します。
assert not right_py3_1 == right_py3_2
assert not right_py3_2 == right_py3_1
assert right_py3_1 != right_py3_2
assert right_py3_2 != right_py3_1
そして、これらが持っていることを思い出してください __ne__
で実装 __eq__
- これは予想される動作ですが、実装は間違っています。
assert not wrong1 == wrong2 # These are contradicted by the
assert not wrong2 == wrong1 # below unexpected behavior!
予期しない動作:
この比較は上記の比較に矛盾することに注意してください(not wrong1 == wrong2
).
>>> assert wrong1 != wrong2
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AssertionError
と、
>>> assert wrong2 != wrong1
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AssertionError
スキップしないでください __ne__
Python 2で
実装をスキップすべきではないという証拠 __ne__
Python 2では、これらの同等のオブジェクトを参照してください。
>>> right_py3_1, right_py3_1child = BaseEquatable(1), ChildComparablePy3(1)
>>> right_py3_1 != right_py3_1child # as evaluated in Python 2!
True
上記の結果はあるべきです False
!
Python 3ソース
デフォルトのcpython実装 __ne__
入っています typeobject.c
の object_richcompare
:
case Py_NE:
/* By default, __ne__() delegates to __eq__() and inverts the result,
unless the latter returns NotImplemented. */
if (self->ob_type->tp_richcompare == NULL) {
res = Py_NotImplemented;
Py_INCREF(res);
break;
}
res = (*self->ob_type->tp_richcompare)(self, other, Py_EQ);
if (res != NULL && res != Py_NotImplemented) {
int ok = PyObject_IsTrue(res);
Py_DECREF(res);
if (ok < 0)
res = NULL;
else {
if (ok)
res = Py_False;
else
res = Py_True;
Py_INCREF(res);
}
}
ここにわかります
しかし、デフォルト __ne__
使用します __eq__
?
Python 3のデフォルト __ne__
Cレベルでの実装の詳細は使用します __eq__
より高いレベルだからです ==
(pyobject_richcompare)効率が低くなります - したがって、それも処理する必要があります NotImplemented
.
もしも __eq__
正しく実装され、次にの否定 ==
また、正しいです - そして、それは私たちが私たちの低レベルの実装の詳細を避けることを可能にします __ne__
.
使用 ==
低レベルのロジックを維持することができます 1 場所、そして 避ける アドレッシング NotImplemented
の __ne__
.
誤ってそれを想定するかもしれません ==
戻るかもしれません NotImplemented
.
実際には、デフォルトの実装と同じロジックを使用します __eq__
, 、アイデンティティをチェックします(参照してください do_richcompare そして以下の私たちの証拠)
class Foo:
def __ne__(self, other):
return NotImplemented
__eq__ = __ne__
f = Foo()
f2 = Foo()
と比較:
>>> f == f
True
>>> f != f
False
>>> f2 == f
False
>>> f2 != f
True
パフォーマンス
私の言葉を見てはいけません。もっとパフォーマンスがあるものを見てみましょう。
class CLevel:
"Use default logic programmed in C"
class HighLevelPython:
def __ne__(self, other):
return not self == other
class LowLevelPython:
def __ne__(self, other):
equal = self.__eq__(other)
if equal is NotImplemented:
return NotImplemented
return not equal
def c_level():
cl = CLevel()
return lambda: cl != cl
def high_level_python():
hlp = HighLevelPython()
return lambda: hlp != hlp
def low_level_python():
llp = LowLevelPython()
return lambda: llp != llp
これらのパフォーマンス数はそれ自体を物語っていると思います:
>>> import timeit
>>> min(timeit.repeat(c_level()))
0.09377292497083545
>>> min(timeit.repeat(high_level_python()))
0.2654011140111834
>>> min(timeit.repeat(low_level_python()))
0.3378178110579029
これを考えると、これは理にかなっています low_level_python
そうでなければCレベルで処理されるPythonでロジックを実行しています。
一部の批評家への対応
別の応答者は書いています:
アーロンホールの実装
not self == other
の__ne__
メソッドは、戻ることができないため、間違っていますNotImplemented
(not NotImplemented
はFalse
)そしてしたがって__ne__
優先度のある方法は決して後退することはありません__ne__
優先されない方法。
持っている __ne__
戻らないでください NotImplemented
間違っていません。代わりに、優先順位付けを処理します NotImplemented
との平等のチェックを介して ==
. 。仮定します ==
正しく実装されており、完了です。
not self == other
以前はデフォルトのPython 3実装でした__ne__
方法でしたが、それはバグであり、Shadowrangerが気づいたように、2015年1月にPython 3.4で修正されました(問題#21408を参照)。
さて、これを説明しましょう。
前述のように、Python 3はデフォルトで処理します __ne__
最初に確認することにより self.__eq__(other)
戻り値 NotImplemented
(シングルトン) - でチェックする必要があります is
もしそうなら返されますが、そうでなければ逆を返すはずです。クラスミックスとして書かれたロジックは次のとおりです。
class CStyle__ne__:
"""Mixin that provides __ne__ functionality equivalent to
the builtin functionality
"""
def __ne__(self, other):
equal = self.__eq__(other)
if equal is NotImplemented:
return NotImplemented
return not equal
これは、CレベルPython APIの正確性に必要であり、Python 3で導入され、作成が行われました。
冗長。すべて関連性があります __ne__
独自のチェックを実装するものや、に委任するものを含む方法を削除しました __eq__
直接または経由 ==
- と ==
そうする最も一般的な方法でした。
結論
Python 2互換コードの場合、使用します ==
実装する __ne__
. 。それはもっとです:
- 正しい
- 単純
- パフォーマンス
Python 3のみで、Cレベルで低レベルの否定を使用します - それは偶数です もっと シンプルでパフォーマンス(プログラマーはそれがあると判断する責任がありますが 正しい).
繰り返しますが、そうしてください いいえ 高レベルのPythonで低レベルのロジックを作成します。
記録のためだけに、標準的に正しく、py2/py3ポータブルを横切る __ne__
見た目:
import sys
class ...:
...
def __eq__(self, other):
...
if sys.version_info[0] == 2:
def __ne__(self, other):
equal = self.__eq__(other)
return equal if equal is NotImplemented else not equal
これは任意のもので動作します __eq__
あなたは定義するかもしれません:
- ようではない
not (self == other)
, 、関係するクラスの1つがの結果が結果であることを暗示していない場合、比較を含むいくつかの迷惑/複雑なケースで干渉しないでください__ne__
の結果と同じですnot
の上__eq__
(例えば、SqlalchemyのORM、両方__eq__
と__ne__
特別なプロキシオブジェクトを返しますTrue
またFalse
, 、そしてしようとしていますnot
結果として__eq__
戻ってきますFalse
, 、正しいプロキシオブジェクトではなく)。 - ようではない
not self.__eq__(other)
, 、これは正しく委任します__ne__
他のインスタンスの場合self.__eq__
戻り値NotImplemented
(not self.__eq__(other)
なぜなら、余分な間違っているだろうからNotImplemented
真実なので、いつ__eq__
比較の実行方法がわからなかった、__ne__
戻ってきますFalse
, 、実際に尋ねられた唯一のオブジェクトが唯一のオブジェクトが等しくなかった場合、2つのオブジェクトが等しいことを暗示しています。
もしあなたの __eq__
使用しません NotImplemented
返品、これは(意味のないオーバーヘッドで)使用する場合、機能します NotImplemented
時々、これにより適切に処理されます。そして、Pythonバージョンのチェックは、クラスが import
-ed python 3で __ne__
未定義のままで、Pythonのネイティブで効率的なフォールバックを許可します __ne__
実装(上記のcバージョン) 引き継ぐ。
なぜこれが必要なのか
Pythonオーバーロードルール
他のソリューションの代わりにこれを行う理由の説明は、やや不可解です。 Pythonには、オペレーターの過負荷に関するいくつかの一般的なルールがあり、特に比較演算子があります。
- (すべてのオペレーターに適用)実行時
LHS OP RHS
, 、 試すLHS.__op__(RHS)
, 、そしてそれが戻ってきた場合NotImplemented
, 、 試すRHS.__rop__(LHS)
. 。例外:ifRHS
のサブクラスですLHS
クラス、テストRHS.__rop__(LHS)
最初. 。比較演算子の場合、__eq__
と__ne__
彼ら自身の「rop」です(したがって、のテスト順序__ne__
はLHS.__ne__(RHS)
, 、 それからRHS.__ne__(LHS)
, 、逆の場合RHS
のサブクラスですLHS
'のクラス) - 「交換」オペレーターのアイデアは別として、演算子間に暗黙の関係はありません。たとえば、同じクラスの場合でも、
LHS.__eq__(RHS)
戻るTrue
意味しませんLHS.__ne__(RHS)
戻り値False
(実際、オペレーターはブール値を返す必要さえありません。SQLalchemyのようなORMは、意図的にそうではなく、より表現力のあるクエリ構文を可能にします)。 Python 3の時点で、デフォルト__ne__
実装はこのように動作しますが、契約上ではありません。オーバーライドできます__ne__
厳格な反対ではない方法で__eq__
.
これがコンパレータの過負荷に適用する方法
したがって、オペレーターを過負荷にすると、2つのジョブがあります。
- 自分で操作を実装する方法を知っている場合は、使用してください それだけ 比較の方法についてのあなた自身の知識(操作の反対側に暗黙的または明示的に委任しないでください。そうすることで、あなたがそれを行う方法に応じて、間違った再帰をリスクリスクします)
- もし、あんたが しないでください 自分で操作を実装する方法を知っている、 いつも 戻る
NotImplemented
, 、そのため、Pythonは他のオペランドの実装に委任できます
の問題 not self.__eq__(other)
def __ne__(self, other):
return not self.__eq__(other)
反対側に委任しないでください(そして、間違っている場合 __eq__
適切に戻ります NotImplemented
)。いつ self.__eq__(other)
戻り値 NotImplemented
(これは「真実」です)、あなたは静かに戻ります False
, 、 それで A() != something_A_knows_nothing_about
戻り値 False
, 、いつかどうかを確認する必要があります something_A_knows_nothing_about
インスタンスと比較する方法を知っていました A
, 、そしてそうでない場合、それは返されるべきでした True
(どちらの側が他者と比較する方法を知らない場合、それらは互いに等しくないと見なされます)。もしも A.__eq__
誤って実装されています(返信 False
それ以外の NotImplemented
反対側を認識していない場合)、これはから「正しい」です A
の視点、戻ってきます True
(以来 A
それが等しいとは思わないので、それは平等ではありません)、しかしそれは間違っているかもしれません something_A_knows_nothing_about
それは決して尋ねなかったので、の視点 something_A_knows_nothing_about
; A() != something_A_knows_nothing_about
終わる True
, 、 しかし something_A_knows_nothing_about != A()
たぶん......だろう False
, 、またはその他の返品値。
の問題 not self == other
def __ne__(self, other):
return not self == other
もっと微妙です。クラスの99%が正しいでしょう。 __ne__
の論理的な逆です __eq__
. 。だが not self == other
上記のルールの両方を破る、つまりクラスを意味する __ne__
そうではありません の論理的逆 __eq__
, 、結果は再び反射的ではありません。なぜなら、オペランドの1つが実装できるかどうかを尋ねられないため __ne__
他のオペランドができない場合でも、まったく。最も単純な例は、戻る変人クラスです False
にとって すべて 比較、だから A() == Incomparable()
と A() != Incomparable()
どちらも戻ります False
. 。の正しい実装で A.__ne__
(戻ってくるもの NotImplemented
比較の方法がわからない場合)、関係は反射的です。 A() != Incomparable()
と Incomparable() != A()
結果に同意する(前者の場合、 A.__ne__
戻り値 NotImplemented
, 、 それから Incomparable.__ne__
戻り値 False
, 、後者にいる間、 Incomparable.__ne__
戻り値 False
直接)。でもいつ A.__ne__
として実装されています return not self == other
, A() != Incomparable()
戻り値 True
(なぜなら A.__eq__
リターンではありません NotImplemented
, 、 それから Incomparable.__eq__
戻り値 False
, 、 と A.__ne__
それを反転させます True
)、 その間 Incomparable() != A()
戻り値 False.
このような例を見ることができます ここ.
明らかに、常に戻るクラス False
両方のための __eq__
と __ne__
少し奇妙です。しかし、前に述べたように、 __eq__
と __ne__
戻る必要さえありません True
/False
; SQLalchemy ORMには、クエリビルディングの特別なプロキシオブジェクトを返すコンパレータを含むクラスがあります。 True
/False
まったく(ブールの文脈で評価された場合、それらは「真実」ですが、そのような文脈で評価されることは決してありません)。
過負荷に失敗することによって __ne__
適切に、あなた 意思 コードとして、その種のクラスを破る:
results = session.query(MyTable).filter(MyTable.fieldname != MyClassWithBadNE())
動作します(sqlalchemyが挿入方法を知っていると仮定します MyClassWithBadNE
SQL文字列にまったく入ります。これは、タイプアダプターなしで実行できます MyClassWithBadNE
まったく協力しなければならない)、予想されるプロキシオブジェクトをに渡す filter
, 、 その間:
results = session.query(MyTable).filter(MyClassWithBadNE() != MyTable.fieldname)
最終的に通過します filter
平野 False
, 、 なぜなら self == other
プロキシオブジェクトを返します not self == other
Truthy Proxyオブジェクトを変換するだけです False
. 。うまくいけば、 filter
のような無効な引数が処理されることの例外をスローします False
. 。私は多くの人がそれを主張するだろうと確信しています MyTable.fieldname
したほうがいい 比較の左側に一貫して、一般的なケースでこれを実施するプログラム的な理由はないという事実と正しいジェネリックが残っています __ne__
どちらの方法でも機能します return not self == other
1つの配置でのみ機能します。
簡単な答え:はい(しかし、それを正しく行うためにドキュメントを読んでください)
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
以前はデフォルトのPython 3実装でした __ne__
方法はバグでしたが、Shadowrangerが気づいたように、2015年1月にPython 3.4で修正されました(参照 問題#21408).
比較演算子の実装
Python言語リファレンス Python 3状態の場合 第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
特定の一組の引数の操作を実装していない場合。これらのメソッドには、交換されたargumentバージョンはありません(左の引数が操作をサポートしていないが、正しい引数が行う場合に使用される)。それよりも、
__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__
方法のため __eq__
メソッド返品 NotImplemented
, 、その逆 not NotImplemented
は False
(なので bool(NotImplemented)
は True
)目的の代わりに 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__
返された方法 "B.__ne__"
, 、 いいえ True
.
次に、デフォルトの実装をオーバーライドするときに何が起こるか見てみましょう 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__
メソッドの場合 __eq__
メソッド返品 NotImplemented
, 、それは間違っています。
すべての場合 __eq__
, __ne__
, __lt__
, __ge__
, __le__
, 、 と __gt__
クラスには意味があり、実装するだけです __cmp__
代わりは。そうでなければ、ダニエル・ディパオロが言ったので、あなたがやっているようにしてください(私はそれを見上げるのではなくテストしていました;))