Comment __eq__ est-il géré en Python et dans quel ordre?

98

Puisque Python ne fournit pas de versions gauche / droite de ses opérateurs de comparaison, comment décide-t-il quelle fonction appeler?

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

Cela semble appeler les deux __eq__fonctions.

Je recherche l'arbre de décision officiel.

PyProg
la source

Réponses:

119

L' a == bexpression invoque A.__eq__, puisqu'elle existe. Son code comprend self.value == other. Puisque les int ne savent pas comment se comparer aux B, Python essaie d'appeler B.__eq__pour voir s'il sait comment se comparer à un int.

Si vous modifiez votre code pour afficher les valeurs comparées:

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

il imprimera:

A __eq__ called: <__main__.A object at 0x013BA070> == <__main__.B object at 0x013BA090> ?
B __eq__ called: <__main__.B object at 0x013BA090> == 3 ?
Ned Batchelder
la source
69

Lorsque Python2.x voit a == b, il essaie ce qui suit.

  • Si type(b)est une classe de style nouveau, et type(b)est une sous-classe de type(a), et type(b)a été remplacée __eq__, alors le résultat est b.__eq__(a).
  • Si type(a)a remplacé __eq__(c'est-à- type(a).__eq__dire non object.__eq__), alors le résultat est a.__eq__(b).
  • Si type(b)a été remplacé __eq__, le résultat est b.__eq__(a).
  • Si aucune des situations ci-dessus n'est le cas, Python répète le processus de recherche __cmp__. S'il existe, les objets sont égaux ssil retourne zero.
  • En guise de solution de secours finale, Python appelle object.__eq__(a, b), qui est Trueiff aet best le même objet.

Si l'une des méthodes spéciales revient NotImplemented, Python agit comme si la méthode n'existait pas.

Notez cette dernière étape attentivement: si ni ani bsurcharge ==, alors a == best le même que a is b.


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

kev
la source
1
Euh, il semble que la documentation de Python 3 était incorrecte. Voir bugs.python.org/issue4395 et le correctif pour plus de précisions. TLDR: sous-classe toujours comparée en premier, même si elle est sur le rhs.
max
Salut kev, joli message. Pourriez-vous expliquer où le premier point est documenté et pourquoi il est conçu comme ça?
wim
1
Ouais où est-ce documenté pour python 2? Est-ce un PEP?
Mr_and_Mrs_D
sur la base de cette réponse et des commentaires accompagnés, cela m'a laissé plus confus qu'auparavant.
Sajuuk
et btw, est-ce que définir une méthode liée __eq__ uniquement sur l'instance d'un type n'est pas suffisant pour que == soit remplacé?
Sajuuk
5

J'écris une réponse mise à jour pour Python 3 à cette question.

Comment est __eq__géré en Python et dans quel ordre?

a == b

Il est généralement entendu, mais pas toujours le cas, qui a == binvoque a.__eq__(b), ou type(a).__eq__(a, b).

Explicitement, l'ordre d'évaluation est:

  1. si ble type de s est une sous-classe stricte (pas du même type) du atype de s et a un __eq__, appelez-le et renvoyez la valeur si la comparaison est implémentée,
  2. Sinon, si aa __eq__, appelez et le retourner si la comparaison est mise en œuvre,
  3. sinon, voyez si nous n'avons pas appelé b's __eq__et qu'il l'a, puis appelez-le et retournez-le si la comparaison est implémentée,
  4. sinon, enfin, faites la comparaison pour l'identité, la même comparaison que is.

Nous savons si une comparaison n'est pas implémentée si la méthode retourne NotImplemented.

(Dans Python 2, il y avait une __cmp__méthode qui a été recherchée, mais elle était obsolète et supprimée dans Python 3.)

Testons nous-mêmes le comportement du premier contrôle en laissant B sous-classe A, ce qui montre que la réponse acceptée est fausse sur ce compte:

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

qui ne s'imprime B __eq__ calledqu'avant le retour False.

Comment connaissons-nous cet algorithme complet?

Les autres réponses ici semblent incomplètes et obsolètes, je vais donc mettre à jour les informations et vous montrer comment vous pouvez rechercher cela par vous-même.

Ceci est géré au niveau C.

Nous devons examiner deux bits de code différents ici - le code par défaut __eq__pour les objets de classe objectet le code qui recherche et appelle la __eq__méthode, qu'elle utilise la méthode par défaut __eq__ou personnalisée.

Défaut __eq__

La recherche __eq__dans les documents pertinents de l'API C nous montre ce qui __eq__est géré par tp_richcompare- qui dans la "object"définition de type dans cpython/Objects/typeobject.cest défini dans object_richcomparepour 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;

Donc ici, si self == othernous retournons True, sinon nous retournons l' NotImplementedobjet. Il s'agit du comportement par défaut pour toute sous-classe d'objets qui n'implémente pas sa propre __eq__méthode.

Comment __eq__est appelé

Ensuite, nous trouvons la documentation de l'API C, la fonction PyObject_RichCompare , qui appelle do_richcompare.

Ensuite, nous voyons que la tp_richcomparefonction, créée pour la "object"définition C est appelée par do_richcompare, alors regardons cela d'un peu plus près.

Le premier contrôle dans cette fonction concerne les conditions des objets comparés:

  • ne sont pas du même type, mais
  • le type du second est une sous-classe du type du premier, et
  • le type du second a une __eq__méthode,

puis appelez la méthode de l'autre avec les arguments permutés, renvoyant la valeur si elle est implémentée. Si cette méthode n'est pas implémentée, nous continuons ...

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

Ensuite, nous voyons si nous pouvons rechercher la __eq__méthode à partir du premier type et l'appeler. Tant que le résultat n'est pas NotImplemented, c'est-à-dire qu'il est implémenté, nous le renvoyons.

    if ((f = Py_TYPE(v)->tp_richcompare) != NULL) {
        res = (*f)(v, w, op);
        if (res != Py_NotImplemented)
            return res;
        Py_DECREF(res);

Sinon, si nous n'avons pas essayé la méthode de l'autre type et qu'elle est là, nous l'essayons, et si la comparaison est implémentée, nous la renvoyons.

    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);
    }

Enfin, nous obtenons une solution de secours au cas où elle ne serait pas implémentée pour l'un ou l'autre type.

Le repli vérifie l'identité de l'objet, c'est-à-dire s'il s'agit du même objet au même endroit en mémoire - c'est le même contrôle que pour 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;

Conclusion

Dans une comparaison, nous respectons d'abord l'implémentation de la sous-classe de la comparaison.

Ensuite, nous tentons la comparaison avec l'implémentation du premier objet, puis avec la seconde s'il n'a pas été appelé.

Enfin, nous utilisons un test d'identité pour comparer l'égalité.

Salle Aaron
la source