Ich schreibe eine aktualisierte Antwort für Python 3 auf diese Frage.
Wie wird __eq__
in Python gehandhabt und in welcher Reihenfolge?
a == b
Es wird allgemein verstanden, aber nicht immer der Fall, dass a == b
aufruft a.__eq__(b)
, oder type(a).__eq__(a, b)
.
Die Reihenfolge der Bewertung lautet explizit:
- Wenn
b
der Typ eine strikte Unterklasse (nicht derselbe Typ) des a
Typs ist und einen hat __eq__
, rufen Sie ihn auf und geben Sie den Wert zurück, wenn der Vergleich implementiert ist.
- wenn sonst,
a
hat __eq__
es nennen , und es zurückgeben , wenn der Vergleich durchgeführt wird,
- Andernfalls sehen Sie, ob wir b's nicht aufgerufen haben
__eq__
und es hat, und rufen Sie es auf und geben Sie es zurück, wenn der Vergleich implementiert ist.
- Andernfalls führen Sie den Vergleich für die Identität durch, den gleichen Vergleich wie
is
.
Wir wissen, ob kein Vergleich implementiert ist, wenn die Methode zurückgegeben wird NotImplemented
.
(In Python 2 gab es eine __cmp__
Methode gesucht, die in Python 3 jedoch veraltet und entfernt wurde.)
Testen wir das Verhalten der ersten Prüfung für uns selbst, indem wir B Unterklasse A lassen, was zeigt, dass die akzeptierte Antwort in dieser Hinsicht falsch ist:
class A:
value = 3
def __eq__(self, other):
print('A __eq__ called')
return self.value == other.value
class B(A):
value = 4
def __eq__(self, other):
print('B __eq__ called')
return self.value == other.value
a, b = A(), B()
a == b
die nur B __eq__ called
vor der Rückkehr gedruckt wird False
.
Woher kennen wir diesen vollständigen Algorithmus?
Die anderen Antworten hier scheinen unvollständig und veraltet zu sein, daher werde ich die Informationen aktualisieren und Ihnen zeigen, wie Sie dies selbst nachschlagen können.
Dies wird auf der C-Ebene behandelt.
Wir müssen uns hier zwei verschiedene Codebits ansehen - den Standard __eq__
für Klassenobjekte object
und den Code, der die __eq__
Methode nachschlägt und aufruft , unabhängig davon, ob sie den Standard __eq__
oder einen benutzerdefinierten Code verwendet .
Standard __eq__
Blick nach __eq__
oben in der entsprechenden C API - Dokumentation zeigt uns , dass __eq__
durch umgegangen wird tp_richcompare
- die in der "object"
Typdefinition in cpython/Objects/typeobject.c
in definiert ist object_richcompare
für case Py_EQ:
.
case Py_EQ:
/* Return NotImplemented instead of False, so if two
objects are compared, both get a chance at the
comparison. See issue #1393. */
res = (self == other) ? Py_True : Py_NotImplemented;
Py_INCREF(res);
break;
Wenn self == other
wir also zurückkehren True
, geben wir das NotImplemented
Objekt zurück. Dies ist das Standardverhalten für jede Unterklasse von Objekten, die keine eigene __eq__
Methode implementiert .
Wie __eq__
wird gerufen
Dann finden wir die C-API-Dokumente, die PyObject_RichCompare- Funktion, die aufruft do_richcompare
.
Dann sehen wir, dass die tp_richcompare
für die "object"
C-Definition erstellte Funktion von aufgerufen wird do_richcompare
. Schauen wir uns das also etwas genauer an.
Die erste Überprüfung in dieser Funktion betrifft die Bedingungen, unter denen die Objekte verglichen werden:
- sind nicht der gleiche Typ, aber
- Der Typ des zweiten ist eine Unterklasse des Typs des ersten und
- Der Typ des zweiten hat eine
__eq__
Methode:
Rufen Sie dann die Methode des anderen mit vertauschten Argumenten auf und geben Sie den Wert zurück, falls implementiert. Wenn diese Methode nicht implementiert ist, fahren wir fort ...
if (!Py_IS_TYPE(v, Py_TYPE(w)) &&
PyType_IsSubtype(Py_TYPE(w), Py_TYPE(v)) &&
(f = Py_TYPE(w)->tp_richcompare) != NULL) {
checked_reverse_op = 1;
res = (*f)(w, v, _Py_SwappedOp[op]);
if (res != Py_NotImplemented)
return res;
Py_DECREF(res);
Als nächstes sehen wir, ob wir die __eq__
Methode vom ersten Typ an nachschlagen und aufrufen können. Solange das Ergebnis nicht NotImplemented ist, dh implementiert ist, geben wir es zurück.
if ((f = Py_TYPE(v)->tp_richcompare) != NULL) {
res = (*f)(v, w, op);
if (res != Py_NotImplemented)
return res;
Py_DECREF(res);
Andernfalls versuchen wir es, wenn wir die Methode des anderen Typs nicht ausprobiert haben und sie vorhanden ist. Wenn der Vergleich implementiert ist, geben wir sie zurück.
if (!checked_reverse_op && (f = Py_TYPE(w)->tp_richcompare) != NULL) {
res = (*f)(w, v, _Py_SwappedOp[op]);
if (res != Py_NotImplemented)
return res;
Py_DECREF(res);
}
Schließlich erhalten wir einen Fallback für den Fall, dass er für keinen der beiden Typen implementiert ist.
Der Fallback prüft auf die Identität des Objekts, dh ob es sich um dasselbe Objekt an derselben Stelle im Speicher handelt. Dies ist dieselbe Prüfung wie für self is other
:
/* If neither object implements it, provide a sensible default
for == and !=, but raise an exception for ordering. */
switch (op) {
case Py_EQ:
res = (v == w) ? Py_True : Py_False;
break;
Fazit
In einem Vergleich respektieren wir zuerst die Unterklassenimplementierung des Vergleichs.
Dann versuchen wir den Vergleich mit der Implementierung des ersten Objekts, dann mit der des zweiten Objekts, wenn es nicht aufgerufen wurde.
Schließlich verwenden wir einen Identitätstest zum Vergleich auf Gleichheit.