Python, sollte ich einen __ne__()
Operator basierend auf implementieren __eq__
?
Kurze Antwort: Implementieren Sie es nicht, aber wenn Sie müssen, verwenden Sie es ==
nicht__eq__
In Python 3 !=
ist die Negation ==
standardmäßig so, dass Sie nicht einmal eine schreiben müssen __ne__
, und die Dokumentation enthält keine Meinung mehr zum Schreiben einer.
Im Allgemeinen schreiben Sie für Nur-Python-3-Code keinen, es sei denn, Sie müssen die übergeordnete Implementierung überschatten, z. B. für ein integriertes Objekt.
Denken Sie also an den Kommentar von Raymond Hettinger :
Die __ne__
Methode folgt automatisch __eq__
nur dann, wenn sie
__ne__
noch nicht in einer Oberklasse definiert ist. Wenn Sie also von einem integrierten System erben, sollten Sie beide überschreiben.
Wenn Sie Ihren Code benötigen, um in Python 2 zu funktionieren, befolgen Sie die Empfehlung für Python 2 und es funktioniert in Python 3 einwandfrei.
In Python 2 implementiert Python selbst keine Operation automatisch in Bezug auf eine andere - daher sollten Sie die __ne__
in Bezug auf ==
anstelle von definieren __eq__
. 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
- Implementierungsoperator
__ne__()
basierend auf __eq__
und
- überhaupt nicht
__ne__
in Python 2 implementiert
bietet in der folgenden Demonstration ein falsches Verhalten.
Lange Antwort
Die Dokumentation zu Python 2 lautet:
Es gibt keine impliziten Beziehungen zwischen den Vergleichsoperatoren. Die Wahrheit von x==y
bedeutet nicht, dass dies x!=y
falsch ist. Dementsprechend sollte beim Definieren __eq__()
auch definiert werden __ne__()
, dass sich die Operatoren wie erwartet verhalten.
Das heißt, wenn wir __ne__
das Gegenteil von definieren __eq__
, können wir ein konsistentes Verhalten erhalten.
Dieser Abschnitt der Dokumentation wurde für Python 3 aktualisiert :
Standardmäßig __ne__()
delegiert __eq__()
und invertiert das Ergebnis, sofern dies nicht der Fall ist NotImplemented
.
und im Abschnitt "Was ist neu" sehen wir, dass sich dieses Verhalten geändert hat:
!=
gibt jetzt das Gegenteil von zurück ==
, es sei denn, es wird ==
zurückgegeben NotImplemented
.
Für die Implementierung __ne__
bevorzugen wir die Verwendung des ==
Operators anstelle der __eq__
direkten Verwendung der Methode. Wenn self.__eq__(other)
eine Unterklasse NotImplemented
für den überprüften Typ zurückgibt , prüft Python dies in other.__eq__(self)
der Dokumentation entsprechend :
Das NotImplemented
Objekt
Dieser Typ hat einen einzelnen Wert. Es gibt ein einzelnes Objekt mit diesem Wert. Auf dieses Objekt wird über den integrierten Namen zugegriffen
NotImplemented
. Numerische Methoden und umfangreiche Vergleichsmethoden können diesen Wert zurückgeben, wenn sie die Operation für die angegebenen Operanden nicht implementieren. (Der Interpreter versucht dann die reflektierte Operation oder einen anderen Fallback, abhängig vom Operator.) Sein Wahrheitswert ist wahr.
Wenn ein reichen Vergleichsoperator gegeben, wenn sie nicht vom gleichen Typ sind, dann prüft Python , wenn das other
ist ein Subtyp, und wenn es , dass die Betreiber definiert hat, verwendet er die other
‚s Methode zuerst (inverse für <
, <=
, >=
und >
). Wenn NotImplemented
zurückgegeben wird , dann verwendet er die Methode des Gegenteils. (Es wird nicht zweimal nach derselben Methode gesucht.) Die Verwendung des ==
Operators ermöglicht diese Logik.
Erwartungen
Semantisch sollten Sie __ne__
im Hinblick auf die Prüfung auf Gleichheit implementieren, da Benutzer Ihrer Klasse erwarten, dass die folgenden Funktionen für alle Instanzen von A gleichwertig sind:
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, beide oben genannten Funktionen sollten immer das gleiche Ergebnis zurückgeben. Dies hängt jedoch vom Programmierer ab.
Demonstration unerwarteten Verhaltens bei der Definition __ne__
basierend auf __eq__
:
Zuerst das Setup:
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."""
Nicht äquivalente Instanzen instanziieren:
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 jede zweite Behauptung der folgenden Aussagen äquivalent und daher logisch redundant zu der vorherigen ist, füge ich sie hinzu, um zu demonstrieren, dass die Reihenfolge keine Rolle spielt, wenn eine eine Unterklasse der anderen ist. )
Diese Instanzen wurden __ne__
implementiert mit ==
:
assert not right1 == right2
assert not right2 == right1
assert right1 != right2
assert right2 != right1
Diese Instanzen, die unter Python 3 getestet werden, funktionieren ebenfalls ordnungsgemäß:
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 denken Sie daran, dass diese __ne__
implementiert wurden mit __eq__
- während dies das erwartete Verhalten ist, ist die Implementierung falsch:
assert not wrong1 == wrong2 # These are contradicted by the
assert not wrong2 == wrong1 # below unexpected behavior!
Unerwartetes Verhalten:
Beachten Sie, dass dieser Vergleich den obigen Vergleichen widerspricht ( 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
Hinweise, dass Sie die Implementierung __ne__
in Python 2 nicht überspringen sollten, finden Sie in den entsprechenden Objekten:
>>> 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 sein False
!
Python 3-Quelle
Die Standard-CPython-Implementierung für __ne__
ist typeobject.c
inobject_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;
Aber die Standard - __ne__
Anwendungen __eq__
?
Das Standardimplementierungsdetail von Python 3 __ne__
auf C-Ebene wird verwendet, __eq__
da die höhere Ebene ==
( PyObject_RichCompare ) weniger effizient wäre - und daher auch behandelt werden muss NotImplemented
.
Wenn __eq__
es korrekt implementiert ist, ==
ist auch die Negation von korrekt - und es ermöglicht uns, Implementierungsdetails auf niedriger Ebene in unserem zu vermeiden __ne__
.
Mit ==
ermöglicht es uns , unsere niedriges Niveau Logik in zu halten , einen Ort, und zu vermeiden Adressierung NotImplemented
in __ne__
.
Man könnte fälschlicherweise annehmen, dass dies ==
zurückkehren könnte NotImplemented
.
Es verwendet tatsächlich dieselbe Logik wie die Standardimplementierung von __eq__
, die auf Identität prüft (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 nicht mein Wort dafür, lassen Sie uns sehen, was performanter ist:
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 Leistungszahlen 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
Dies ist sinnvoll, wenn Sie bedenken, dass low_level_python
in Python Logik ausgeführt wird, die sonst auf C-Ebene behandelt würde.
Reaktion auf einige Kritiker
Ein anderer Antwortender schreibt:
Die Implementierung not self == other
der __ne__
Methode durch Aaron Hall ist falsch, da sie niemals zurückkehren kann NotImplemented
( not NotImplemented
ist False
) und daher die __ne__
Methode, die Priorität hat, niemals auf die __ne__
Methode zurückgreifen kann, die keine Priorität hat.
Mit __ne__
nie Rückkehr NotImplemented
macht es nicht falsch. Stattdessen behandeln wir die Priorisierung mit NotImplemented
über die Prüfung auf Gleichheit mit ==
. Vorausgesetzt, es ==
ist korrekt implementiert, sind wir fertig.
not self == other
war früher die Standard-Python 3-Implementierung der __ne__
Methode, aber es war ein Fehler und wurde im Januar 2015 in Python 3.4 korrigiert, wie ShadowRanger bemerkte (siehe Problem Nr. 21408).
Nun, lassen Sie uns das erklären.
Wie bereits erwähnt, behandelt Python 3 standardmäßig __ne__
zuerst, ob self.__eq__(other)
zurückgegeben wird NotImplemented
(ein Singleton). Dies sollte überprüft is
und zurückgegeben werden, wenn dies der Fall ist. Andernfalls sollte die Umkehrung zurückgegeben werden. Hier ist diese Logik, die als Klassenmixin geschrieben wurde:
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 für die Korrektheit der Python-API auf C-Ebene erforderlich und wurde in Python 3 eingeführt
redundant. Alle relevanten __ne__
Methoden wurden entfernt, einschließlich derer, die ihre eigene Prüfung implementieren, sowie solcher, die __eq__
direkt oder über delegieren ==
- und dies ==
war die häufigste Methode.
Ist Symmetrie wichtig?
Unser hartnäckiger Kritiker liefert ein pathologisches Beispiel, um die Handhabung NotImplemented
zu __ne__
rechtfertigen und Symmetrie über alles zu bewerten. Lassen Sie uns das Argument mit einem klaren Beispiel stählern:
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)
Nach dieser Logik müssen wir also, um die Symmetrie aufrechtzuerhalten, die komplizierte __ne__
Version unabhängig von der Python-Version schreiben .
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)
Anscheinend sollten wir uns nicht darum kümmern, dass diese Instanzen gleich und nicht gleich sind.
Ich schlage vor, dass Symmetrie weniger wichtig ist als die Annahme eines vernünftigen Codes und das Befolgen der Hinweise in der Dokumentation.
Wenn A jedoch eine vernünftige Implementierung von hätte __eq__
, könnten wir hier immer noch meiner Richtung folgen und hätten immer noch Symmetrie:
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)
Fazit
Verwenden Sie ==
zum Implementieren von Python 2-kompatiblem Code __ne__
. Es ist mehr:
- richtig
- einfach
- performant
In Python 3 nur, verwenden Sie die Low-Level - Negation auf der C - Ebene - es ist noch mehr einfache und performante (obwohl der Programmierer für die Bestimmung verantwortlich ist , dass es richtig ).
Schreiben Sie auch hier keine Low-Level-Logik in High-Level-Python.
__ne__
verwenden__eq__
, nur , dass Sie es implementieren.