Python, sollte ich __ne __ () Operator auf __eq__ Basis implementieren?
-
08-10-2019 - |
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?
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, dassx!=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, dassx!=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 istNotImplemented
.
und in der "Was ist neu" Abschnitt , sehen wir dieses Verhalten hat sich geändert:
!=
kehrt nun das Gegenteil von dem==
, es sei denn,==
kehrtNotImplemented
.
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 nieNotImplemented
(not NotImplemented
istFalse
) 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- die
__ne__
Methoden in diesem Patch schließen Ausgabe 21408 und - die
__ne__
Methoden in der Follow-on-Bereinigung hier entfernt
ü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 vonnot
auf__eq__
(zB SQLAlchemy ORM, wo beide__eq__
und__ne__
Rückkehr spezielle Proxy-Objekte, nichtTrue
oderFalse
, und zu versuchen, das Ergebnis dernot
__eq__
würdeFalse
zurückzukehren, anstatt das richtige Proxy-Objekt). - Im Gegensatz zu
not self.__eq__(other)
, dies richtig Delegierten den__ne__
der anderen Instanz, wennself.__eq__
kehrtNotImplemented
(not self.__eq__(other)
zusätzliche falsch wäre, weilNotImplemented
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:
- (Gilt für alle Betreiber) Beim Laufen
LHS OP RHS
, tryLHS.__op__(RHS)
, und wenn dieser zurückkehrtNotImplemented
, versuchenRHS.__rop__(LHS)
. Ausnahme: WennRHS
ist eine Unterklasse vonLHS
Klasse, dann TestRHS.__rop__(LHS)
zuerst . Im Falle der Vergleichsoperatoren,__eq__
und__ne__
sind, ihre eigenen „rop“ s (so der Prüfauftrag für__ne__
istLHS.__ne__(RHS)
, dannRHS.__ne__(LHS)
, rückgängig gemacht, wennRHS
ist eine Unterklasse vonLHS
Klasse) - 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ückkehrTrue
nichtLHS.__ne__(RHS)
kehrtFalse
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:
- 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)
- 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
Anrufex.__lt__(y)
,x<=y
nenntx.__le__(y)
,x==y
ruftx.__eq__(y)
,x!=y
ruftx.__ne__(y)
,x>y
Anrufex.__gt__(y)
undx>=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 istNotImplemented
. Es gibt keine andere implizierte Beziehungen zwischen den Vergleichsoperator, zum Beispiel der Wahrheit von(x<y or x==y)
impliziertx<=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 gibtNotImplemented
; - die
__eq__
Methode gibt einen anderen Wert alsNotImplemented
.
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__"
-
!=
ruftA.__ne__
. -
A.__ne__
ruftA.__eq__
. -
A.__eq__
kehrtNotImplemented
. -
!=
ruftB.__ne__
. -
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
-
!=
ruftA.__ne__
. -
A.__ne__
ruftA.__eq__
. -
A.__eq__
kehrtTrue
. -
!=
kehrtnot True
, das heißtFalse
.
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
-
!=
ruftA.__ne__
. -
A.__ne__
ruft==
. -
==
ruftA.__eq__
. -
A.__eq__
kehrtNotImplemented
. -
==
ruftB.__eq__
. -
B.__eq__
kehrtNotImplemented
. -
==
kehrtA() is B()
, das heißtFalse
. -
A.__ne__
kehrtnot False
, das heißtTrue
.
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
-
!=
ruftA.__ne__
. -
A.__ne__
ruft==
. -
==
ruftA.__eq__
. -
A.__eq__
kehrtTrue
. -
A.__ne__
kehrtnot True
, das heißtFalse
.
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;))