記録のために、標準的に正しいクロス 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__
定義する可能性のあるもので機能します。
- とは異なり、関係するクラスの 1 つが の結果がonの結果と同じ
not (self == other)
であることを意味しない比較を含む、煩わしい/複雑なケースでは干渉しません(たとえば、 と の両方が特別なプロキシ オブジェクトを返すSQLAlchemy の ORM 、またはではなく、正しいプロキシ オブジェクトではなく、would returnの結果を試みます)。__ne__
not
__eq__
__eq__
__ne__
True
False
not
__eq__
False
- とは異なり、これは他のインスタンスの に
not self.__eq__(other)
正しく委譲します(が返された場合 ( は非常に間違っています。なぜならが真実であるため、比較の実行方法がわからない場合はが返されます。これは、2 つのオブジェクトが実際には唯一の尋ねられたオブジェクトには見当がつきませんでした。これは、デフォルトで等しくないことを意味します)__ne__
self.__eq__
NotImplemented
not self.__eq__(other)
NotImplemented
__eq__
__ne__
False
__eq__
リターンを使用しない場合NotImplemented
、これは (無意味なオーバーヘッドで) 機能し、時々使用するNotImplemented
場合は、適切に処理されます。また、Python バージョン チェックは、クラスがimport
Python 3 で -edの場合、__ne__
未定義のままにして、Python のネイティブで効率的なフォールバック__ne__
実装 (上記の C バージョン)が引き継ぐことを意味します。
なぜこれが必要なのか
Python オーバーロード ルール
他のソリューションの代わりにこれを行う理由の説明は、やや難解です。Python には、演算子のオーバーロード、特に比較演算子に関するいくつかの一般的な規則があります。
- (すべての演算子に適用) 実行
LHS OP RHS
中は を試しLHS.__op__(RHS)
、それが を返す場合はNotImplemented
を試しRHS.__rop__(LHS)
ます。例外:が のクラスRHS
のサブクラスである場合は、最初LHS
に をテストします。比較演算子の場合、とは独自の「rop」です (つまり、 のテスト順序は であり、が のクラスのサブクラスである場合は逆になります)RHS.__rop__(LHS)
__eq__
__ne__
__ne__
LHS.__ne__(RHS)
RHS.__ne__(LHS)
RHS
LHS
- 「交換された」演算子のアイデアは別として、演算子間に暗黙の関係はありません。同じクラスのインスタンスであっても、
LHS.__eq__(RHS)
返すことは返すTrue
ことを意味しません(実際、演算子はブール値を返す必要さえありません。SQLAlchemy のような ORM は意図的にそうしないため、より表現力豊かなクエリ構文が可能になります)。Python 3 の時点で、デフォルトの実装はこのように動作しますが、これは契約上のものではありません。の正反対ではない方法でオーバーライドできます。LHS.__ne__(RHS)
False
__ne__
__ne__
__eq__
これがコンパレータのオーバーロードにどのように適用されるか
したがって、演算子をオーバーロードすると、次の 2 つのジョブが発生します。
- 操作を自分で実装する方法を知っている場合は、比較の方法に関する自分の知識のみを使用して実行してください (暗黙的または明示的に、操作の反対側に委任しないでください。そうすると、不正確および/または無限再帰のリスクが生じます。やり方次第)
- 操作を自分で実装する方法がわからない場合は、常にreturnを返してください
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
はより微妙です。__ne__
の論理的逆であるすべてのクラスを含め、クラスの 99% で正しくなります__eq__
。しかしnot self == other
、上記のルールの両方を破ります。つまり、がの論理反転で__ne__
はない__eq__
クラスの場合、結果は再び非対称になります__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__
ません。SQLAlchemy ORM には、 /ではなく、クエリ構築用の特別なプロキシ オブジェクトを返すコンパレータを持つクラスがあります(ブール コンテキストで評価された場合は「真実」ですが、そのようなコンテキストで評価されることは決して想定されていません)。True
False
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
、プレーンを渡すことになります。うまくいけば、のような無効な引数を処理すると例外がスローされます。一貫して比較の左側にあるべきだと多くの人が主張するだろうと確信していますが、一般的なケースでこれを強制するプログラム上の理由はなく、正しいジェネリックはどちらの方法でも機能しますが、機能するのは1 つの配置で。False
self == other
not self == other
False
filter
False
MyTable.fieldname
__ne__
return not self == other