Python、__ eq__に基づいて__ne __()演算子を実装する必要がありますか?


98

__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がこれらの演算子を使用する方法に欠けているものがあるので、これは良い考えではありませんか?

回答:


57

はい、それで問題ありません。実際、ドキュメントでは、次のものを定義する__ne__ときに定義するように求めています__eq__

比較演算子間に暗黙の関係はありません。の真実は、x==yそれx!=y が偽であることを意味しません。したがって、を定義するとき は、演算子が期待どおりに動作__eq__()する__ne__()ように定義する必要もあります。

多くの場合(この場合など)は、の結果を否定するのと同じくらい簡単__eq__ですが、常にそうとは限りません。


12
これが正しい答えです(ここで、@ aaron-hallが作成)。あなたが引用したドキュメント、あなたがそれを実装することだけを__ne__使って__eq__、あなたがを使って実装することを勧めませ
Guyarad

2
@guyarad:実際、適切に委任しないため、アーロンの答えはまだ少し間違っています。代わりに治療をNotImplementedに委任する手がかりとして、片側からの復帰を__ne__反対側に、not self == other(オペランドの仮定で__eq__暗黙的に委譲する他のオペランドを比較する方法を知りません)__eq__、それを反転、その後、他の側から。SQLAlchemy ORMのフィールドなど、奇妙なタイプの場合、これにより問題が発生します。
ShadowRanger

1
ShadowRangerの批判は非常に病理学的なケース(IMHO)にのみ適用され、以下の私の回答で完全に対処されます。
アーロンホール

1
新しいドキュメント(少なくとも3.7の場合は、それよりも古い可能性があります)は__ne__自動的に委任さ__eq__れ、この回答の引用はドキュメントに存在しません。結論としては、実装__eq__して__ne__委任するだけで完全にpythonicになります。
bluesummers

132

Python、に__ne__()基づいて演算子を実装する必要があり__eq__ますか?

短い答え:あなたがしなければならない場合の髪型、使用して実装ではなく==、ではありません__eq__

Python 3では、!===デフォルトでの否定であるため、を作成する必要すらありません__ne__。また、ドキュメントを作成することについて、ドキュメントはもはや考えられていません。

一般的に言って、Python 3のみのコードの場合、組み込みオブジェクトなどの親の実装に影を付ける必要がない限り、コードを記述しないでください。

つまり、レイモンドヘッティンガーのコメントを覚えておいてください。

この__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__メソッドを直接使用するのではなく演算子を使用することをお勧めします。これ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)

予想される行動:

(注:以下の各毎秒アサーションが同等であるため、論理的にその前の1への冗長ながら、私は彼らがそれを証明するために含めています、一方が他方のサブクラスであるときの順序は重要ではありません。

これらのインスタンスは次のように__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 (Py_TYPE(self)->tp_richcompare == NULL) {
        res = Py_NotImplemented;
        Py_INCREF(res);
        break;
    }
    res = (*Py_TYPE(self)->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);
        }
    }
    break;

しかし、デフォルトで__ne____eq__

より高いレベル(PyObject_RichCompare)は効率が低いため__ne__、CレベルでのPython 3のデフォルトの実装詳細が使用します。したがって、これも処理する必要があります。__eq__==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、Pythonでロジックを実行している場合にCレベルで処理されることになると考えると理にかなっています。

一部の批評家への対応

別の回答者はこう書いている:

Aaron Hallのメソッドの実装not self == otherは、__ne__決して戻ることができないNotImplementednot NotImplementedis False)ため、正しくありません。したがって、__ne__優先順位のあるメソッドは、優先順位のない__ne__メソッドにフォールバックできません。

持っ__ne__決して復帰することはNotImplemented、それは間違ったことはありません。代わりに、NotImplementedとの等価性のチェックを介してで優先順位付けを処理し==ます。仮定すると、==正しく実装されて、私たちは完了です。

not self == other以前は__ne__メソッドのデフォルトのPython 3実装でしたが、それはバグであり、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__直接または経由==-そして==そうすることの最も一般的な方法でした。

対称性は重要ですか?

私たちの執念深い批評家は、何よりも対称性を重視してで処理NotImplementedすること__ne__を主張する病理学的例を提供します。明確な例を挙げて議論を鋼鉄でやろう:

class B:
    """
    this class has no __eq__ implementation, but asserts 
    any instance is not equal to any other object
    """
    def __ne__(self, other):
        return True

class A:
    "This class asserts instances are equivalent to all other objects"
    def __eq__(self, other):
        return True

>>> A() == B(), B() == A(), A() != B(), B() != A()
(True, True, False, True)

したがって、このロジックでは、対称性を維持するために__ne__、Pythonのバージョンに関係なく、複雑なを記述する必要があります。

class B:
    def __ne__(self, other):
        return True

class A:
    def __eq__(self, other):
        return True
    def __ne__(self, other):
        result = other.__eq__(self)
        if result is NotImplemented:
            return NotImplemented
        return not result

>>> A() == B(), B() == A(), A() != B(), B() != A()
(True, True, True, True)

どうやら、これらのインスタンスが等しいことも等しくないことも気にする必要はありません。

対称性は賢明なコードの推定よりも重要ではなく、ドキュメントのアドバイスに従うことを提案します。

ただし、Aにの賢明な実装がある場合は、__eq__ここで私の方向性をたどることができ、対称性が維持されます。

class B:
    def __ne__(self, other):
        return True

class A:
    def __eq__(self, other):
        return False         # <- this boolean changed... 

>>> A() == B(), B() == A(), A() != B(), B() != A()
(False, False, True, True)

結論

Python 2互換コードの場合は、==を実装するために使用します__ne__。それ以上です:

  • 正しい
  • 簡単な
  • パフォーマンス

Python 3のみで、Cレベルで低レベルの否定を使用します。これはさらにシンプルでパフォーマンスが高くなります(ただし、プログラマが正しいかどうかを判断する必要があります)。

ここでも、高レベルのPythonで低レベルのロジックを記述しないでください。


3
優れた例!驚きの一部は、「右側」の反射を伴ういくつかの魔法のメソッドとは異なり、オペランドの順序はまったく問題にならないことです。私が見逃した部分(そして私に多くの時間を費やしました)を繰り返すために:コードが演算子の左側にスーパークラスまたはサブクラスを持っているかどうかに関係なく、サブクラスの豊富な比較メソッドが最初に試行されます。これが、a1 != c2返された理由ですFalse---実行されませんでしたがa1.__ne__ミックスインのメソッドc2.__ne__が無効になりました。は真実なので、です。 __eq__NotImplementednot NotImplementedFalse
Kevin J. Chase

2
最近の更新は、のパフォーマンス上の利点をうまく示していますがnot (self == other)、それが高速ではない(とにかく、Py2の他のどのオプションよりも高速である)と主張している人はいません。問題は、場合によっては間違っていることです。Python自体は以前はそうでしたが、任意のサブクラスがあると正しくnot (self == other)なかったため変更されました。間違った答えへの最速はまだ間違っています。
ShadowRanger 2018

1
具体的な例は本当に重要ではありません。問題は、あなたの実装で、つまり、あなたの行動__ne__に委譲し__eq__、(両側必要に応じての)が、それは決してにフォールバックしません__ne__、両方が__eq__"あきらめて"いる場合でも、相手側の動作に。正しい__ne__デリゲートはそれ自体 __eq__に委任されますがNotImplemented、それが返された場合は、フォールバックして反対側のに移動します。反対側を__ne__反転するのではなく__eq__(反対側が明示的にへの委任をオプトインしていない可能性があるため__eq__、その決定を下すことです)。
ShadowRanger

1
@AaronHall:これを今日再検討しても、サブクラスの実装には通常問題がないと思います(サブクラスを壊すことは非常に複雑であり、親の完全な知識があると想定されているサブクラスはそれを回避できるはずです) )。しかし、私は私の答えに複雑ではない例を示しました。非病理学的なケースは、SQLAlchemyのORMの、どちらで__eq____ne__リターンのいずれかTrueまたはFalseではなく、(「truthy」であることを起こる)プロキシオブジェクト。誤って実装__ne__することは、比較の順序の問題を意味します(1つの順序でのみプロキシを取得します)。
ShadowRanger

1
明確にするために、99%(または99.999%)のケースでは、ソリューションは問題なく、そして(明らかに)より高速です。しかし、あなたはそれがケースを制御できないのでではないの罰金を、そのコードが他の人が使用することができるライブラリーライター(読み:何もなく、シンプルなワンオフもっぱら個人的な使用のためのスクリプトやモジュール)として、あなたがする必要があります正しい実装を使用して、オペレーターのオーバーロードに関する一般規約に準拠し、発生する可能性のあるその他のコードを処理します。幸いなことに、Py3では、__ne__完全に省略できるため、これは問題になりません。今から1年後、Py2は死んでしまい、これは無視します。:-)
ShadowRanger

10

参考までに、標準的に正しいクロス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)関連するクラスの一つは、結果があることを意味するものではありません比較を含むいくつかの迷惑な/複雑なケースに干渉しない、__ne__の結果と同じであるnot__eq__(両方とも例えばSQLAlchemyのORMの、__eq__そして__ne__、特別なプロキシオブジェクトを返すとTrueまたはFalseではなくnot、の結果を試行すると、正しいプロキシオブジェクトではなく__eq__が返されFalseます)。
  • とは異なりnot self.__eq__(other)、これは戻り__ne__時に他のインスタンスのに正しく委譲しself.__eq__ますNotImplementednot self.__eq__(other)これNotImplementedは真実であるため余分に間違っています。したがって__eq__、比較を実行する方法がわからなかった場合は__ne__が戻りFalse、実際には唯一の場合に2つのオブジェクトが等しいことを意味します尋ねられたオブジェクトは、デフォルトが等しくないことを意味する考えを持っていませんでした)

あなたがいる場合__eq__は使用しませんNotImplemented戻り、(無意味なオーバーヘッドで)この作品、それは使用しない場合はNotImplemented、適切に時々 、このハンドルを。また、Pythonバージョンチェックは、クラスがimportPython 3で-edされた場合、__ne__未定義のままになり、Pythonのネイティブで効率的なフォールバック__ne__実装(上記のCバージョン)が引き継ぐことを可能にします。


なぜこれが必要なのか

Pythonオーバーロードルール

他のソリューションの代わりにこれを行う理由の説明はやや難解です。Pythonには、オーバーロード演算子、特に比較演算子に関するいくつかの一般的なルールがあります。

  1. (すべての演算子に適用されます)を実行しているときにをLHS OP RHS試しLHS.__op__(RHS)、が返された場合はNotImplementedを試しRHS.__rop__(LHS)ます。例外:RHSがクラスのサブクラスである場合LHSRHS.__rop__(LHS) 最初にテストします。比較演算子の場合、__eq__および__ne__自分の「ROP」Sであり(のためのテスト順序がそう__ne__であるLHS.__ne__(RHS)、その後RHS.__ne__(LHS)、場合逆RHSのサブクラスであるLHSのクラス)
  2. 「スワップされた」演算子の概念は別として、演算子間に暗黙の関係はありません。同じクラスのインスタンスであっても、LHS.__eq__(RHS)戻るTrueことは意味しませんLHS.__ne__(RHS)は返すFalse(実際、演算子はブール値を返す必要さえありません。SQLAlchemyのようなORMは意図的にそうしないので、より表現力のあるクエリ構文が可能になります)。Python 3以降、デフォルトの__ne__実装はこのように動作しますが、契約ではありません。の__ne__正反対ではない方法でオーバーライドできます__eq__

これがコンパレータのオーバーロードにどのように適用されるか

したがって、演算子をオーバーロードすると、2つのジョブがあります。

  1. 自分で操作を実装する方法を知っている場合は、それを使用して、 は、比較を行う方法について自分の知識のみして実行してください(操作の反対側に、暗黙的または明示的に委任ないでください。そうすると、不正確さや無限再帰のリスクが生じます。やり方次第です)
  2. 自分で操作を実装する方法がわからない場合は、常に返すNotImplementedので、Pythonは他のオペランドの実装に委任できます

の問題 not self.__eq__(other)

def __ne__(self, other):
    return not self.__eq__(other)

反対側にデリゲートすることはありません(__eq__適切にを返すと正しくありませんNotImplemented)。ときにself.__eq__(other)戻ってNotImplemented(「truthy」である)、あなた静かに復帰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_aboutA() != 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__それが実現できるかどうかのオペランドのいずれかが尋ねたことがないので、結果は、再び非対称な__ne__全てにおいても他の場合には、オペランドはできません。最も単純な例は、すべての比較で返さFalseれる奇妙なクラスなので、両方とも返されます。正しい実装(比較の方法がわからないときに戻るもの)の場合、関係は対称的です。そしてA() == Incomparable()A() != Incomparable()FalseA.__ne__NotImplementedA() != Incomparable()Incomparable() != A()結果に同意します(前者の場合はをA.__ne__返しNotImplemented、次にをIncomparable.__ne__返しますFalseIncomparable.__ne__False直接戻ります)。しかし場合A.__ne__として実装されreturn not self == otherA() != 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がMyClassWithBadNESQL文字列に挿入する方法を知っていると仮定します。これはタイプアダプターMyClassWithBadNEを使用して、まったく連携する必要なしに実行できます)。予想されるプロキシオブジェクトをに渡しますfilter

 results = session.query(MyTable).filter(MyClassWithBadNE() != MyTable.fieldname)

プロキシオブジェクトを返し、真のプロキシオブジェクトをに変換するだけfilterFalseので、最終的にはプレーンを渡すことになりself == otherます。うまくいけば、などの無効な引数が処理されると例外がスローされます。多くの人が一貫して比較の左側にあるべきだと主張するだろうと私は確信していますが、一般的なケースではこれを強制するプログラム上の理由はなく、正しいジェネリックがどちらの方法でも機能するという事実が残っていますnot self == otherFalsefilterFalseMyTable.fieldname __ne__return not self == other機能するだけです1つの配置で。


1
唯一の正しい、完全で正直な(申し訳ありませんが@AaronHall)回答。これは受け入れられる答えになるはずです。
Maggyero

4

短い答え:はい(ただし、ドキュメントを読んで正しく実行してください)

ShadowRangerの__ne__メソッドの実装は正しいものです(__ne__Python 3.4以降のメソッドのデフォルトの実装です):

def __ne__(self, other):
    result = self.__eq__(other)

    if result is not NotImplemented:
        return not result

    return NotImplemented

どうして?それは重要な数学的特性、オペレーターの対称性を保持するため!=です。この演算子はバイナリなので、その結果は、1つだけではなく、両方のオペランドの動的な型に依存する必要があります。これは、複数のディスパッチを可能にするプログラミング言語のダブルディスパッチを介して実装れます。Juliaなど)の。シングルディスパッチのみを許可するPythonでは、数値メソッド豊富な比較メソッドに対して値を返すことでダブルディスパッチがシミュレートされますNotImplemented、他のオペランドのタイプをサポートしない実装メソッドでを。次に、インタプリタは他のオペランドの反映されたメソッドを試します。

アーロン・ホールの実装not self == other__ne__それはの対称削除するなどの方法が間違ってい!=演算子を。確かに、それは決して戻ることはできませんNotImplementednot NotImplemented is False)をため、__ne__優先順位の高い__ne__メソッドが優先順位の低いメソッドにフォールバックすることは決してありません。not self == other以前は__ne__メソッドのデフォルトのPython 3実装でしたが、ShadowRangerが気づいたように、それは2015年1月のPython 3.4で修正されたバグでした(問題#21408を参照))。

比較演算子の実装

Python 3 のPython言語リファレンスでは、第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。calls x.__lt__(y)x<=ycalls x.__le__(y)x==ycallsx.__eq__(y)x!=ycalls x.__ne__(y)x>ycalls x.__gt__(y)、calls 、x>=y calls x.__ge__(y)です。

リッチ比較メソッドはNotImplemented、指定された引数のペアに対する操作を実装していない場合、シングルトンを返すことがあります。

これらのメソッドの交換された引数のバージョンはありません(左側の引数が操作をサポートしていないが、右側の引数はサポートしている場合に使用されます)。むしろ、__lt__()かつ__gt__()互いの反射があり、__le__()かつ__ge__()互いの反射であり、 __eq__()そして__ne__()自分自身の反射です。オペランドのタイプが異なり、右オペランドのタイプが左オペランドのタイプの直接または間接サブクラスである場合、右オペランドのリフレクトメソッドが優先され、それ以外の場合は左オペランドのメソッドが優先されます。仮想サブクラス化は考慮されません。

これをPythonコードに変換するoperator_eq==、(for 、operator_nefor !=operator_ltfor <operator_gtfor >operator_lefor <=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 NotImplementedFalsebool(NotImplemented)ですTrue)の代わりに希望のNotImplemented

の不適切な実装 __ne__

Aaron Hallは上記で示したようnot self.__eq__(other)に、__ne__メソッドのデフォルトの実装ではありません。しかし、そうでもありませんnot self == other後者は、デフォルトの実装の動作とnot self == other2つの場合の実装の動作を比較することによって、以下に示されています。

  • __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__"
  1. !=を呼び出しますA.__ne__
  2. A.__ne__を呼び出しますA.__eq__
  3. A.__eq__を返しますNotImplemented
  4. !=を呼び出しますB.__ne__
  5. 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
  1. !=を呼び出しますA.__ne__
  2. A.__ne__を呼び出しますA.__eq__
  3. A.__eq__を返しますTrue
  4. !=not Trueつまりを返しますFalse

これは、この場合、A.__ne__メソッドがメソッドの逆を返すことを示していますA.__eq__。したがって、__ne__メソッドは、ドキュメントに記載されているように動作します。

のデフォルト実装のオーバーライド A.__ne__メソッドの上記の正しい、同じ結果が得られます。

not self == other 実装

A.__ne__メソッドのデフォルト実装を実装でオーバーライドしnot self == otherA.__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
  1. !=を呼び出しますA.__ne__
  2. A.__ne__を呼び出します==
  3. ==を呼び出しますA.__eq__
  4. A.__eq__を返しますNotImplemented
  5. ==を呼び出しますB.__eq__
  6. B.__eq__を返しますNotImplemented
  7. ==A() is B()つまりを返しますFalse
  8. 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
  1. !=を呼び出しますA.__ne__
  2. A.__ne__を呼び出します==
  3. ==を呼び出しますA.__eq__
  4. A.__eq__を返しますTrue
  5. A.__ne__not Trueつまりを返しますFalse

この場合、__ne__メソッドのデフォルトの実装も返さFalseれます。

この実装は__ne____eq__メソッドがを返すときに、メソッドのデフォルト実装の動作を複製できないためNotImplemented、正しくありません。


最後の例へ:「この実装は__ne____eq__メソッドがNotImplementedを返したときにメソッドのデフォルト実装の動作を複製できないため、正しくありません。」- A無条件の等価性を定義します。したがって、A() == B()。したがって、A() != B() Falseである必要があります。与えられた例は病的なものです(つまり__ne__、文字列を返さ__eq__ない__ne____ne__ください。依存するべきではなく__eq__、Python 3のデフォルトの期待であるに依存する必要があります)。あなたが私の考えを変えることができるまで、私はまだこの答えで-1です。
アーロンホール

@AaronHall Python言語リファレンス:「豊かな比較方法は、シングルトンを返すことがありNotImplemented、それは、引数の与えられたペアの操作を実装していない場合は、通常、。FalseそしてTrue成功した比較のために返されます。しかし、これらの方法は、任意の値を返すことができますなので、比較演算子がブールコンテキストで使用されている場合(ifステートメントの条件など)、Pythonはbool()値を呼び出して結果がtrueかfalseかを判断します。
Maggyero

@AaronHallの実装は__ne__、重要な数学的特性である演算子の対称性を無効にし!=ます。この演算子はバイナリであるため、その結果は、1つだけではなく、両方のオペランドの動的な型に依存する必要があります。これは、複数のディスパッチを可能にする言語のダブルディスパッチを介してプログラミング言語に正しく実装されています。シングルディスパッチのみを許可するPythonでは、値を返すことでダブルディスパッチがシミュレートされます。NotImplemented
Maggyero

最後の例は、2つのクラスがあり、Bその戻りのためのすべてのチェックにtruthy文字列__ne__、およびAその戻りTrueのためのすべてのチェックに__eq__。これは病的な矛盾です。そのような矛盾の下で、例外を上げるのが最善でしょう。の知識がなければBAは対称性の目的でBの実装を尊重する義務を負いません__ne__。例のその時点では、A実装方法__ne__は私には関係ありません。あなたの主張をするために、実用的で非病理的なケースを見つけてください。私はあなたに対処するために私の答えを更新しました。
アーロンホール

@AaronHallより現実的な例については、@ ShadowRangerによるSQLAlchemyの例を参照してください。また、の実装が__ne__通常の使用例で機能するという事実は、それを正しく行わないことに注意してください。ボーイング737 MAX航空機は、墜落前に500,000便を飛行しました…
マギーエロ

-1

すべての場合は__eq____ne____lt____ge____le__、および__gt__クラスのためのメイク感覚、そしてちょうど実装する__cmp__代わりに。それ以外の場合は、ダニエル・ディパオロが言った(私がそれを調べるのではなくテストしている間)ので、あなたがしているようにしてください;))


12
__cmp__()あなたが豊富な比較演算子を使用してに慣れるべきように、特別な方法は、もはやのPython 3.xではサポートされていません。
Don O'Donnell

8
あるいは、Python 2.7または3.xを使用している場合は、functools.total_orderingデコレーターも非常に便利です。
アダムパーキン

ヘッドアップをありがとう。とはいえ、昨年一年半でそういうことに気づきました。;)
Karl Knechtel
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.