Wie wird __eq__ in Python behandelt und in welcher Reihenfolge?

96

Wie entscheidet Python, welche Funktion aufgerufen werden soll, da Python keine Links / Rechts-Versionen seiner Vergleichsoperatoren bereitstellt?

class A(object):
    def __eq__(self, other):
        print "A __eq__ called"
        return self.value == other
class B(object):
    def __eq__(self, other):
        print "B __eq__ called"
        return self.value == other

>>> a = A()
>>> a.value = 3
>>> b = B()
>>> b.value = 4
>>> a == b
"A __eq__ called"
"B __eq__ called"
False

Dies scheint beide __eq__Funktionen aufzurufen .

Ich suche den offiziellen Entscheidungsbaum.

PyProg
quelle

Antworten:

119

Der a == bAusdruck wird aufgerufen A.__eq__, da er existiert. Sein Code enthält self.value == other. Da int's nicht wissen, wie sie sich mit B's vergleichen sollen, versucht Python aufzurufenB.__eq__ zu sehen, ob es weiß, wie man sich mit einem int vergleicht.

Wenn Sie Ihren Code ändern, um anzuzeigen, welche Werte verglichen werden:

class A(object):
    def __eq__(self, other):
        print("A __eq__ called: %r == %r ?" % (self, other))
        return self.value == other
class B(object):
    def __eq__(self, other):
        print("B __eq__ called: %r == %r ?" % (self, other))
        return self.value == other

a = A()
a.value = 3
b = B()
b.value = 4
a == b

es wird gedruckt:

A __eq__ called: <__main__.A object at 0x013BA070> == <__main__.B object at 0x013BA090> ?
B __eq__ called: <__main__.B object at 0x013BA090> == 3 ?
Ned Batchelder
quelle
68

Wenn Python2.x sieht a == b, wird Folgendes versucht.

  • Wenn type(b)es sich um eine Klasse neuen Stils type(b)handelt, die eine Unterklasse von type(a)ist und type(b)überschrieben wurde __eq__, ist das Ergebnis b.__eq__(a).
  • Wenn type(a)überschrieben wurde __eq__( dh type(a).__eq__nicht object.__eq__), ist das Ergebnisa.__eq__(b) .
  • Wenn type(b)überschrieben wurde __eq__, ist das Ergebnis b.__eq__(a).
  • Wenn dies nicht der Fall ist, wiederholt Python den gesuchten Vorgang __cmp__. Wenn es existiert, sind die Objekte gleich, wenn es zurückgegeben wird zero.
  • Als letzten Fallback ruft Python auf object.__eq__(a, b), das Trueiff ist aund bdasselbe Objekt ist.

Wenn eine der speziellen Methoden zurückgegeben wird NotImplemented, verhält sich Python so, als ob die Methode nicht vorhanden wäre.

Beachten Sie den letzten Schritt sorgfältig: Wenn weder anoch büberlastet ==, dann a == bist das gleiche wie a is b.


Von https://eev.ee/blog/2012/03/24/python-faq-equality/

kev
quelle
1
Ähh, es scheint, dass die Python 3-Dokumente falsch waren. Weitere Informationen finden Sie unter bugs.python.org/issue4395 und im Patch. TLDR: Unterklasse immer noch zuerst verglichen, auch wenn es auf der rechten Seite ist.
Max
Hallo kev, schöner Beitrag. Können Sie erklären, wo der erste Aufzählungspunkt dokumentiert ist und warum er so gestaltet ist?
wim
1
Ja, wo ist das für Python 2 dokumentiert? Ist es ein PEP?
Mr_and_Mrs_D
Aufgrund dieser Antwort und der dazugehörigen Kommentare war ich nur verwirrter als zuvor.
Sajuuk
und übrigens, reicht es nicht aus, eine gebundene Methode __eq__ nur für die Instanz eines Typs zu definieren, um == zu überschreiben?
Sajuuk
2

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 == baufruft a.__eq__(b), oder type(a).__eq__(a, b).

Die Reihenfolge der Bewertung lautet explizit:

  1. Wenn bder Typ eine strikte Unterklasse (nicht derselbe Typ) des aTyps ist und einen hat __eq__, rufen Sie ihn auf und geben Sie den Wert zurück, wenn der Vergleich implementiert ist.
  2. wenn sonst, ahat __eq__es nennen , und es zurückgeben , wenn der Vergleich durchgeführt wird,
  3. 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.
  4. 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__ calledvor 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 objectund 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.cin definiert ist object_richcomparefü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 == otherwir also zurückkehren True, geben wir das NotImplementedObjekt 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_richcomparefü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.

Aaron Hall
quelle