Frage

Ich habe eine Klasse, wo ich die __eq__() Operator außer Kraft setzen möchten. Es scheint sinnvoll zu sein, dass ich den __ne__() Betreiber als auch außer Kraft setzen sollte, aber macht es Sinn __ne__ basierend auf __eq__ als solche zu implementieren?

class A:
    def __eq__(self, other):
        return self.value == other.value

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

Oder gibt es etwas, dass ich mit der Art und Weise Python bin fehle verwendet diese Operatoren, dass dies keine gute Idee macht?

War es hilfreich?

Lösung

Ja, das ist völlig in Ordnung. In der Tat, die Dokumentation __ne__ definieren drängt, wenn Sie __eq__ definieren:

Es gibt keine impliziten Beziehungen unter den Vergleichsoperatoren. Das Wahrheit x==y bedeutet nicht, dass x!=y ist falsch. Dementsprechend wird, wenn die Definition __eq__() sollte man auch __ne__() definieren, so dass die Betreiber verhalten sich wie erwartet.

In vielen Fällen (wie diese), wird es so einfach sein wie das Ergebnis der __eq__ negiert, aber nicht immer.

Andere Tipps

Python, soll ich __ne__() Operator basierend auf __eq__ implementieren?

Kurze Antwort: Nein. Die Nutzung == statt der __eq__

In Python 3 ist != die Negation der == standardmäßig, so dass Sie nicht einmal erforderlich, um eine __ne__ zu schreiben, und die Dokumentation nicht mehr eigenwillig auf einem Schreiben.

Im Allgemeinen für Python 3-only-Code, schreiben Sie nicht ein, wenn Sie die übergeordnete Implementierung in den Schatten stellen müssen, z.B. für ein builtin Objekt.

Das heißt, im Kopf behalten Raymond Hettinger Kommentar :

Die __ne__ Methode ergibt sich automatisch aus __eq__ nur dann, wenn __ne__ nicht bereits in einer übergeordneten Klasse definiert. Also, wenn Sie von einem eingebauten vererben, ist es am besten beide außer Kraft zu setzen.

Wenn Sie Ihren Code zur Arbeit in Python benötigen 2, folgt der Empfehlung für Python 2 und es wird in Python 3 gut funktionieren.

In Python 2 Python selbst implementieren nicht automatisch eine Operation in Bezug auf die anderen - deshalb sollten Sie die __ne__ in Bezug auf == anstelle des __eq__ definieren. Z.B.

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)`

Siehe Beweis dafür, dass

  • Umsetzung __ne__() Betreiber basierend auf __eq__ und
  • nicht __ne__ in Python 2 bei allen Implementierung

liefert falsches Verhalten in der Demonstration unten.

Lange Antwort

Die Dokumentation für Python 2 sagt:

Es gibt keine impliziten Beziehungen zwischen den Vergleichsoperator. Das Wahrheit x==y bedeutet nicht, dass x!=y falsch ist. Dementsprechend wird, wenn Definieren __eq__() sollte man auch __ne__() definieren, so dass die Operatoren verhalten sich wie erwartet.

Das heißt also, dass, wenn wir __ne__ in Bezug auf die Umkehrung von __eq__ definieren, können wir ein konsistentes Verhalten bekommen.

In diesem Abschnitt der Dokumentation wurde für Python 3 aktualisiert:

In der Standardeinstellung __ne__() Delegierten __eq__() und umkehrt das Ergebnis es sei denn, es ist NotImplemented.

und in der "Was ist neu" Abschnitt , sehen wir dieses Verhalten hat sich geändert:

  • != kehrt nun das Gegenteil von dem ==, es sei denn, == kehrt NotImplemented.

Für __ne__ Implementierung bevorzugen wir den == Operator verwenden stattdessen die __eq__ Methode der Verwendung direkt so, dass, wenn self.__eq__(other) eine Unterklasse kehrt NotImplemented für den Typen überprüft, Python wird in geeigneter Weise überprüfen other.__eq__(self) Aus der Dokumentation :

Das NotImplemented Objekt

Dieser Typ hat einen einzigen Wert. Es gibt ein einzelnes Objekt mit diesem Wert. Diese Aufgabe wird durch den integrierten Namen zugegriffen NotImplemented. Numerische Methoden und reiche Vergleichsmethoden können zurückkehren dieser Wert, wenn sie nicht implementieren die Operation für die Operanden unter der Voraussetzung. (Der Dolmetscher wird dann versuchen, den reflektierten Betrieb oder einiger anderer Rückfall, je nach Betreiber.) Sein Wahrheitswert wahr.

Wenn ein reichen Vergleichsoperator gegeben,wenn sie nicht vom gleichen Typ sind, Python überprüft, ob der other ist ein Subtyp, und wenn es, dass die Betreiber definiert hat, verwendet er die Methode des other ersten (inverse für <, <=, >= und >). Wenn NotImplemented zurückgegeben wird, und verwendet es die Methode des Gegenteils. (Es tut nicht Nach der gleichen Methode zweimal.) Mit Hilfe des == Operator ermöglicht diese Logik zu.


Die Erwartungen

Semantisch, sollten Sie __ne__ im Hinblick auf die Prüfung auf Gleichheit, weil die Benutzer Ihrer Klasse implementieren werden die folgenden Funktionen erwarten für alle Instanzen von A äquivalent zu sein.

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

Das heißt, sollte sowohl der oben genannten Funktionen immer Rückkehr das gleiche Ergebnis. Aber dies ist auf dem Programmierer abhängig.

Demonstration von unerwartetem Verhalten, wenn __ne__ definiert, basierend auf __eq__:

Zuerst wird die Einstellung:

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."""

Instantiate antivalenter Instanzen:

right1, right2 = ComparableRight(1), ChildComparableRight(2)
wrong1, wrong2 = ComparableWrong(1), ChildComparableWrong(2)
right_py3_1, right_py3_2 = BaseEquatable(1), ChildComparablePy3(2)

Erwartetes Verhalten:

(Hinweis: Während jeder zweite Behauptung von jedem der unten entspricht und daher mit dem einen logisch redundanten, bevor es, ich bin mit ihnen, dass demonstrieren um keine Rolle, wenn man eine Unterklasse der andere ist . )

Diese Fälle haben __ne__ mit == umgesetzt:

assert not right1 == right2
assert not right2 == right1
assert right1 != right2
assert right2 != right1

Diese Instanzen, die Prüfung unter Python 3, auch korrekt funktionieren:

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

Und erinnere sich, dass diese __ne__ mit __eq__ umgesetzt haben - während dies das erwartete Verhalten ist, die Umsetzung ist falsch:

assert not wrong1 == wrong2         # These are contradicted by the
assert not wrong2 == wrong1         # below unexpected behavior!

unerwartetes Verhalten:

Beachten Sie, dass dieser Vergleich im Widerspruch zu den oben Vergleichen (not wrong1 == wrong2).

>>> assert wrong1 != wrong2
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AssertionError

und

>>> assert wrong2 != wrong1
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AssertionError

überspringen Sie nicht __ne__ in Python 2

Für Hinweise darauf, dass Sie nicht der Umsetzung __ne__ in Python 2 überspringen sollte, sehen diese äquivalenten Objekte:

>>> right_py3_1, right_py3_1child = BaseEquatable(1), ChildComparablePy3(1)
>>> right_py3_1 != right_py3_1child # as evaluated in Python 2!
True

Das obige Ergebnis sollte False sein!

Python 3-Quelle

Der Standard CPython Implementierung für __ne__ ist in typeobject.c in 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);
            }
        }

Hier sehen wir,

Aber der Standard __ne__ Anwendungen __eq__?

default __ne__ Implementierung Detail Python 3 auf den C-Ebene Anwendungen __eq__ weil die höhere Ebene == ( PyObject_RichCompare ) wäre weniger effizient -. und deshalb muss es auch NotImplemented behandeln

Wenn __eq__ korrekt umgesetzt wird, dann ist die Negation der == ist auch richtig -. Und es erlaubt uns, geringe Implementierungsdetails in unserem __ne__ zu vermeiden

== Verwendung erlaubt es uns, unsere niedriges Niveau Logik zu halten in einen Ort und vermeiden Adressierung NotImplemented in __ne__.

Man könnte fälschlicherweise, dass == annehmen kann NotImplemented zurück.

Es benutzt die gleiche Logik wie die Standardimplementierung von __eq__, das prüft, ob Identität (siehe do_richcompare und unsere Beweise unten)

class Foo:
    def __ne__(self, other):
        return NotImplemented
    __eq__ = __ne__

f = Foo()
f2 = Foo()

Und die Vergleiche:

>>> f == f
True
>>> f != f
False
>>> f2 == f
False
>>> f2 != f
True

Performance

Nehmen Sie mein Wort nicht für sie, mal sehen, was mehr performant:

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

ich denke, diese Leistung Zahlen sprechen für sich:

>>> 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

Das macht sEnse, wenn Sie, dass low_level_python betrachten Logik in Python tut, die sonst auf der C-Ebene behandelt werden würde.

Antwort auf einige Kritiker

Eine andere Antworter schreibt:

Aaron Hall Implementierung not self == other der __ne__ Methode ist falsch, da es nie NotImplemented (not NotImplemented ist False) und damit die __ne__ Methode, die Priorität hat, kann niemals fallen zurück auf der __ne__ Methode, die nicht Priorität hat zurückkehren kann.

Mit __ne__ nie Rückkehr NotImplemented macht es nicht falsch. Stattdessen behandeln wir Priorisierung mit NotImplemented über die Prüfung auf Gleichheit mit ==. == Unter der Annahme korrekt umgesetzt wird, sind wir fertig.

not self == other verwendete die Standard-Python 3 Implementierung der __ne__ Methode zu sein, aber es war ein Fehler, und es wurde in Python 3.4 auf Januar 2015 korrigiert, wie ShadowRanger bemerkt (siehe Ausgabe # 21408).

Nun, lassen Sie uns dies erklären.

Wie bereits erwähnt, Python 3 durch Standardgriffe __ne__, indem zuerst überprüft, ob self.__eq__(other) kehrt NotImplemented (ein Singleton) - die für die mit is geprüft werden soll und zurückgegeben, wenn dem so ist, sonst sollte es die inverse zurückzukehren. Hier ist die Logik als eine Klasse mixin geschrieben:

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

Dies ist notwendig für Richtigkeit für C-Ebene Python-API, und es wurde in Python 3, so dass

eingeführt

überflüssig. Alle relevanten __ne__ Methoden wurden entfernt, darunter auch solche, ihre eigene Kontrolle sowie diejenigen, die Umsetzung, dass Vertreter der __eq__ direkt oder über == -. Und == war die häufigste Art und Weise, dies zu tun

Fazit

Für Python 2 kompatiblen Code, Verwendung == __ne__ zu implementieren. Es ist mehr:

  • richtig
  • einfach
  • performant

In Python 3 nur, verwenden Sie die Low-Level-Negation auf der C-Ebene - es ist auch mehr einfacher und performante (obwohl der Programmierer ist verantwortlich für die Bestimmung, dass es richtig ).

Auch hier tun nicht Schreib Low-Level-Logik in hohem Niveau Python.

Nur für das Protokoll, ein kanonisch korrekter und Quer Py2 / PY3 portable __ne__ würde wie folgt aussehen:

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

Dies funktioniert mit jedem __eq__ Sie definieren können:

  • Im Gegensatz zu not (self == other), nicht stört in einigen lästigen / komplexen Fällen Vergleiche bestehen, bei denen einer der beteiligten Klassen bedeutet nicht, dass das Ergebnis der __ne__ ist das gleiche wie das Ergebnis von not auf __eq__ (zB SQLAlchemy ORM, wo beide __eq__ und __ne__ Rückkehr spezielle Proxy-Objekte, nicht True oder False, und zu versuchen, das Ergebnis der not __eq__ würde False zurückzukehren, anstatt das richtige Proxy-Objekt).
  • Im Gegensatz zu not self.__eq__(other), dies richtig Delegierten den __ne__ der anderen Instanz, wenn self.__eq__ kehrt NotImplemented (not self.__eq__(other) zusätzliche falsch wäre, weil NotImplemented truthy ist, also wenn __eq__ nicht wußte, wie der Vergleich durchzuführen, __ne__ False zurückkehren würde, was bedeutet, dass die beiden Objekte, wenn in der Tat gleich waren, fragte der einzige Gegenstand hat keine Ahnung, was eine Verzug nicht gleich bedeuten würde)

Wenn Ihr __eq__ verwendet nicht NotImplemented zurückgibt, das funktioniert (mit sinnlosen Overhead), wenn sie die Verwendung NotImplemented tut manchmal, diese Griffe es richtig. Und die Python Versionsprüfung bedeutet, dass, wenn die Klasse import-ed in Python 3 ist, __ne__ undefiniert bleibt, so dass Python nativen, effizienter Rückfall __ne__ Implementierung (eine C-Version der oben) zu übernehmen.


Warum ist dies erforderlich

Python Überlastung Regeln

Die Erklärung, warum Sie dies tun, anstatt zu anderen Lösungen ist etwas obskur. Python hat ein paar allgemeine Regeln über Überlastung Operatoren und Vergleichsoperatoren insbesondere:

  1. (Gilt für alle Betreiber) Beim Laufen LHS OP RHS, try LHS.__op__(RHS), und wenn dieser zurückkehrt NotImplemented, versuchen RHS.__rop__(LHS). Ausnahme: Wenn RHS ist eine Unterklasse von LHS Klasse, dann Test RHS.__rop__(LHS) zuerst . Im Falle der Vergleichsoperatoren, __eq__ und __ne__ sind, ihre eigenen „rop“ s (so der Prüfauftrag für __ne__ ist LHS.__ne__(RHS), dann RHS.__ne__(LHS), rückgängig gemacht, wenn RHS ist eine Unterklasse von LHS Klasse)
  2. Abgesehen von der Idee der „getauscht“ Betreiber, gibt es keine implizite Beziehung zwischen den Betreibern. Auch zum Beispiel der gleichen Klasse, LHS.__eq__(RHS) Rückkehr True nicht LHS.__ne__(RHS) kehrt False bedeutet (in der Tat sind die Betreiber nicht einmal erforderlich Boolesche Werte zurückzukehren; ORMs wie SQLAlchemy absichtlich nicht, für eine ausdrucksAbfrageSyntax erlaubt). Ab Python 3, verhält sich die Standard __ne__ Implementierung auf diese Weise, aber es ist nicht verbindlich; Sie können __ne__ in einer Weise überschreiben, die nicht streng Gegensätze von __eq__ sind.

Wie dies gilt für Überlastung Komparatoren

Also, wenn Sie einen Operator überlasten, haben Sie zwei Aufgaben:

  1. Wenn Sie wissen, wie die Operation selbst zu implementieren, tun so, mit nur Ihr eigenes Wissen, wie man den Vergleich zu tun (nie delegieren, implizit oder explizit, auf die andere Seite der Operation; dabei riskiert Unrichtigkeit und / oder unendliche Rekursion, je nachdem, wie Sie es tun)
  2. Wenn Sie nicht wissen, wie die Operation selbst zu implementieren, immer Rückkehr NotImplemented, so Python kann an die anderen Operanden-Implementierung delegieren

Das Problem mit not self.__eq__(other)

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

nie Delegierten der anderen Seite (und ist falsch, wenn __eq__ NotImplemented richtig zurückgibt). Wenn self.__eq__(other) kehrt NotImplemented (die „truthy“ ist), kehren Sie leise False, so A() != something_A_knows_nothing_about kehrt False, wenn es überprüft haben sollte, wenn something_A_knows_nothing_about wusste, wie man Fälle von A zu vergleichen, und wenn es nicht der Fall ist, sollte es wieder True haben (da, wenn keine der beiden Seiten weiß, wie man den anderen zu vergleichen, sind sie als einander nicht gleich). Wenn A.__eq__ falsch implementiert wird (False statt NotImplemented zurückkehrt, wenn es nicht auf der anderen Seite erkennen), dann ist diese „richtige“ aus A Sicht ist, True Rückkehr (da A hält es nicht gleich ist, so dass es nicht gleich ist), aber es könnte falsch aus something_A_knows_nothing_about Sicht sein, da es nicht einmal something_A_knows_nothing_about gefragt; A() != something_A_knows_nothing_about endet True, aber something_A_knows_nothing_about != A() konnte False oder jeden anderen Rückgabewert.

Das Problem mit not self == other

def __ne__(self, other):
    return not self == other

ist subtiler. Es wird für 99% der Klassen korrekt sein, einschließlich aller Klassen, für die __ne__ die logische Inverse von __eq__ ist. Aber not self == other bricht beide der oben genannten Regeln, die Mittel für die Klassen, in denen __ne__ nicht die logische Inverse von __eq__, sind die Ergebnisse wieder nicht-reflexiv, weil einer der Operanden wird nie gefragt, ob es kann __ne__ überhaupt, auch wenn der andere Operand kann nicht implementieren. Das einfachste Beispiel ist eine Weirdo-Klasse, die False für alle Vergleiche, so A() == Incomparable() zurück und A() != Incomparable() beide Rückkehr False. Mit einer korrekten Umsetzung von A.__ne__ (eine, die Renditen NotImplemented wenn sie nicht wissen, wie der Vergleich zu tun), die Beziehung ist reflexiv; A() != Incomparable() und Incomparable() != A() einig über das Ergebnis (weil im ersteren Fall, A.__ne__ kehrt NotImplemented, dann Incomparable.__ne__ kehrt False, während im letzteren Incomparable.__ne__ kehrt False direkt). Aber wenn A.__ne__ implementiert als return not self == other, A() != Incomparable() kehrt True (weil A.__eq__ zurückkehrt, nicht NotImplemented, dann Incomparable.__eq__ kehrt False und A.__ne__ umkehrt, das zu True), während Incomparable() != A() kehrt False.

Sie können ein Beispiel dafür in Aktion finden Sie unter hier .

Offensichtlich ist eine Klasse, die immer False sowohl kehrt für __eq__ und __ne__ ist ein wenig seltsam. Aber wie bereits erwähnt, __eq__ und __ne__ brauchen noch nicht einmal True / False zurückzukehren; die SQLAlchemy ORM hat Klassen mit Komparatoren, dass die Renditen eine spezielle Proxy-Objekt für die Erstellung von Abfragen, nicht True / False bei allen (sie sind „truthy“, wenn in einem Booleschen Kontext ausgewertet, aber sie sind nie in einem solchen Kontext bewertet werden soll ).

Durch das Versäumnis generacodic zu überlastenetagcode richtig, Sie wird Pause Klassen dieser Art, wie der Code:

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

arbeitet (vorausgesetzt SQLAlchemy weiß, wie __ne__ in eine SQL-Zeichenfolge überhaupt einzufügen, dies mit Typ-Adapter ohne MyClassWithBadNE getan werden kann, überhaupt zur Zusammenarbeit mit), das erwarteten Proxy-Objekt zu MyClassWithBadNE vorbei, während:

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

wird nach oben vorbei filter einer einfachen filter beenden, weil False ein Proxy-Objekt zurückgibt, und self == other wandelt nur das truthy Proxy-Objekt zu not self == other. Hoffentlich wirft False eine Ausnahme auf ungültige Argumente wie filter gehandhabt wird. Während ich sicher, dass viele bin wird, dass False argumentieren sollte auf der linken Seite des Vergleichs konsequent sein, bleibt die Tatsache, dass es kein programmatischer Grund, dies im allgemeinen Fall zu erzwingen, und ein korrekter generic MyTable.fieldname wird so oder so arbeiten, während __ne__ nur in einer Anordnung funktioniert.

Kurze Antwort: Ja (aber die Dokumentation zu lesen, es zu tun rechts)

ShadowRanger Implementierung der __ne__ Methode der richtigen ist (in dem Sinne, dass es genau wie die Standard-Python 3-Implementierung verhält):

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

    if result is not NotImplemented:
        return not result

    return NotImplemented

Aaron Hall Implementierung not self == other der __ne__ Methode ist falsch, da es nie NotImplemented (not NotImplemented ist False) und damit die __ne__ Methode, die Priorität hat, kann niemals fallen zurück auf der __ne__ Methode, die nicht Priorität hat zurückkehren kann. not self == other verwendete die Standard-Python 3 Implementierung der __ne__ Methode zu sein, aber es war ein Fehler, und es wurde in Python 3.4 auf Januar 2015 korrigiert, wie ShadowRanger bemerkt (siehe Ausgabe # 21408 ).

Umsetzung der Vergleichsoperator

Die Python-Sprachreferenz für Python 3 Zustände in seinem Kapitel III Datenmodell :

object.__lt__(self, other)
object.__le__(self, other)
object.__eq__(self, other)
object.__ne__(self, other)
object.__gt__(self, other)
object.__ge__(self, other)

Dies sind die sogenannten „reichen Vergleich“ Methoden. Die Korrespondenz zwischen Operator Symbolen und Methodennamen sind wie folgt: x<y Anrufe x.__lt__(y), x<=y nennt x.__le__(y), x==y ruft x.__eq__(y), x!=y ruft x.__ne__(y), x>y Anrufe x.__gt__(y) und x>=y x.__ge__(y) nennt.

Ein reiches Vergleichsverfahren kann die Singleton NotImplemented zurück, wenn es nicht implementiert die Operation für ein gegebenes Paar von Argumenten.

Es gibt keine ausgelagerten Argument Versionen dieser Methoden (Einsatz wenn das linke Argument unterstützt nicht den Betrieb, sondern das Recht Argument der Fall ist); vielmehr sind __lt__() und __gt__() jeweils anderen Reflexion, __le__() und __ge__() sind einander Reflexion und __eq__() und __ne__() sind, ihre eigene Reflexion. Wenn die Operanden ist von verschiedenen Arten, und die Art des rechten Operanden ist ein direkte oder indirekte Unterklasse des Typs des linken Operanden, das reflektierte Verfahren der rechte Operand hat Priorität, da sonst die Methode des linken Operanden hat Vorrang. Virtuelle Subklassifizieren wird nicht berücksichtigt.

Die Umsetzung dieser in Python-Code gibt (mit operator_eq für ==, operator_ne für !=, operator_lt für <, operator_gt für >, operator_le für <= und operator_ge für >=):

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

Standardimplementierung der Vergleichsmethoden

Die Dokumentation ergänzt:

In der Standardeinstellung __ne__() Delegierten __eq__() und umkehrt das Ergebnis es sei denn, es ist NotImplemented. Es gibt keine andere implizierte Beziehungen zwischen den Vergleichsoperator, zum Beispiel der Wahrheit von (x<y or x==y) impliziert x<=y nicht.

Die Standardimplementierung der Vergleichsmethoden (__eq__, __ne__, __lt__, __gt__, __le__ und __ge__) kann somit durch angegeben werden:

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

Das ist also die korrekte Umsetzung der __ne__ Methode. Und es gibt nicht immer die Umkehrung der __eq__ Methode, weil, wenn die __eq__ Methode zurück NotImplemented, sein inverser not NotImplemented ist False (als bool(NotImplemented)True ist) anstelle des gewünschten NotImplemented.

Falsche Implementierungen von __ne__

Als Aaron Hall demonstriert oben, not self.__eq__(other) ist nicht die Standardimplementierung der __ne__ Methode. . Aber noch not self == other ist Letzteres unten gezeigt wird durch das Verhalten der Standardimplementierung Vergleich mit dem Verhalten der not self == other Umsetzung in zwei Fällen:

  • die __eq__ Methode gibt NotImplemented;
  • die __eq__ Methode gibt einen anderen Wert als NotImplemented.

Standardimplementierung

Lassen Sie uns sehen, was passiert, wenn die A.__ne__ Methode die Standardimplementierung und die A.__eq__ Methode gibt NotImplemented verwendet:

class A:
    pass


class B:

    def __ne__(self, other):
        return "B.__ne__"


assert (A() != B()) == "B.__ne__"
  1. != ruft A.__ne__.
  2. A.__ne__ ruft A.__eq__.
  3. A.__eq__ kehrt NotImplemented.
  4. != ruft B.__ne__.
  5. B.__ne__ kehrt "B.__ne__".

Dies zeigt, dass, wenn die A.__eq__ Methode zurückgibt NotImplemented, die A.__ne__ Methode auf dem B.__ne__ Verfahren fällt zurück.

Nun wollen wir sehen, was passiert, wenn die A.__ne__ Methode die Standardimplementierung und die A.__eq__ Methode gibt einen anderen Wert als NotImplemented verwendet:

class A:

    def __eq__(self, other):
        return True


class B:

    def __ne__(self, other):
        return "B.__ne__"


assert (A() != B()) is False
  1. != ruft A.__ne__.
  2. A.__ne__ ruft A.__eq__.
  3. A.__eq__ kehrt True.
  4. != kehrt not True, das heißt False.

Dies zeigt, dass in diesem Fall gibt das A.__ne__ Verfahren die Umkehrung der A.__eq__ Methode. So sind die __ne__ Methode verhält sich in der Dokumentation beworben mag.

Aufschalten der Standardimplementierung der A.__ne__ Methode mit der korrekten Implementierung über Ausbeuten bei gleichen Ergebnissen.

not self == other Implementierung

Lassen Sie uns sehen, was passiert, wenn das Überschreiben der Standardimplementierung der A.__ne__ Methode mit dem not self == other Implementierung und der A.__eq__ Methode gibt 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. != ruft A.__ne__.
  2. A.__ne__ ruft ==.
  3. == ruft A.__eq__.
  4. A.__eq__ kehrt NotImplemented.
  5. == ruft B.__eq__.
  6. B.__eq__ kehrt NotImplemented.
  7. == kehrt A() is B(), das heißt False.
  8. A.__ne__ kehrt not False, das heißt True.

Die Standardimplementierung der __ne__-Methode zurück "B.__ne__", nicht True.

Nun wollen wir sehen, was passiert, wenn das Überschreiben der Standardimplementierung der A.__ne__ Methode mit dem not self == other Implementierung und den A.__eq__ Methode gibt einen anderen Wert als 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. != ruft A.__ne__.
  2. A.__ne__ ruft ==.
  3. == ruft A.__eq__.
  4. A.__eq__ kehrt True.
  5. A.__ne__ kehrt not True, das heißt False.

Die Standardimplementierung der __ne__ Methode auch False in diesem Fall zurückgegeben.

Da diese Implementierung nicht das Verhalten der Standardimplementierung des generacod zu replizierenicetagcode Methode, wenn die __ne__ Methode zurückgibt __eq__, es ist falsch.

all __eq__ Wenn __ne__, __lt__, __ge__, __le__ und __gt__ Sinn für die Klasse, dann implementieren nur __cmp__ statt. Ansonsten tun, wie Sie tun, wegen der etwas sagte Daniel DiPaolo (während ich es testen anstatt es von oben schaut;))

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top