記録のために、標準的に正しいクロス 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__TrueFalsenot__eq__False
- とは異なり、これは他のインスタンスの に
not self.__eq__(other)正しく委譲します(が返された場合 ( は非常に間違っています。なぜならが真実であるため、比較の実行方法がわからない場合はが返されます。これは、2 つのオブジェクトが実際には唯一の尋ねられたオブジェクトには見当がつきませんでした。これは、デフォルトで等しくないことを意味します)__ne__self.__eq__NotImplementednot self.__eq__(other)NotImplemented__eq____ne__False
__eq__リターンを使用しない場合NotImplemented、これは (無意味なオーバーヘッドで) 機能し、時々使用するNotImplemented場合は、適切に処理されます。また、Python バージョン チェックは、クラスがimportPython 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)RHSLHS
- 「交換された」演算子のアイデアは別として、演算子間に暗黙の関係はありません。同じクラスのインスタンスであっても、
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返します。他のものと比較すると、それらは互いに等しくないと見なされます)。が正しく実装されていない場合 (反対側を認識しない場合の代わりに戻る) 、これは の観点からは "正しい" であり、返されます(等しいとは考えていないため、等しくないため)。から間違っているFalsesomething_A_knows_nothing_aboutATrueA.__eq__FalseNotImplementedATrueAsomething_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()FalseA.__ne__NotImplementedA() != Incomparable()Incomparable() != A()結果に同意します(前者の場合は をA.__ne__返しNotImplemented、次に をIncomparable.__ne__返しますがFalse、後者の場合は直接Incomparable.__ne__返しますFalse)。しかし、A.__ne__が として実装されているreturn not self == other場合は(ではなくをA() != Incomparable()返し、次に を返し、それを に反転するため) を返します。TrueA.__eq__NotImplementedIncomparable.__eq__FalseA.__ne__TrueIncomparable() != A()False.
この例は、ここで実際に見ることができます。
False明らかに、常に両方__eq__を返すクラスで__ne__、少し奇妙です。しかし、前述のように、/を返す必要さえ__eq__あり__ne__ません。SQLAlchemy ORM には、 /ではなく、クエリ構築用の特別なプロキシ オブジェクトを返すコンパレータを持つクラスがあります(ブール コンテキストで評価された場合は「真実」ですが、そのようなコンテキストで評価されることは決して想定されていません)。TrueFalseTrueFalse
__ne__適切にオーバーロードしないと、次のコードのように、そのようなクラスが壊れます。
results = session.query(MyTable).filter(MyTable.fieldname != MyClassWithBadNE())
動作し (SQLAlchemy がMyClassWithBadNESQL 文字列に挿入する方法をまったく知っていると仮定します。これは、型アダプターを使用MyClassWithBadNEして、まったく協力する必要なく実行できます)、予想されるプロキシ オブジェクトを に渡しますfilter。
results = session.query(MyTable).filter(MyClassWithBadNE() != MyTable.fieldname)
はプロキシ オブジェクトを返し、真のプロキシ オブジェクトを に変換するだけなのでfilter、プレーンを渡すことになります。うまくいけば、のような無効な引数を処理すると例外がスローされます。一貫して比較の左側にあるべきだと多くの人が主張するだろうと確信していますが、一般的なケースでこれを強制するプログラム上の理由はなく、正しいジェネリックはどちらの方法でも機能しますが、機能するのは1 つの配置で。Falseself == othernot self == otherFalsefilterFalseMyTable.fieldname __ne__return not self == other