¿Cómo se maneja __eq__ en Python y en qué orden?

99

Dado que Python no proporciona versiones izquierda / derecha de sus operadores de comparación, ¿cómo decide qué función llamar?

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

Esto parece llamar a ambas __eq__funciones.

Estoy buscando el árbol de decisión oficial.

PyProg
fuente

Respuestas:

119

La a == bexpresión invoca A.__eq__, ya que existe. Su código incluye self.value == other. Dado que los int no saben cómo compararse con los B, Python intenta invocar B.__eq__para ver si sabe cómo compararse con un int.

Si modifica su código para mostrar qué valores se están comparando:

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

imprimirá:

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

Cuando Python2.x ve a == b, intenta lo siguiente.

  • Si type(b)es una clase de estilo nuevo y type(b)es una subclase de type(a)y se type(b)ha anulado __eq__, entonces el resultado es b.__eq__(a).
  • Si se type(a)ha anulado __eq__(es decir, type(a).__eq__no lo es object.__eq__), el resultado es a.__eq__(b).
  • Si se type(b)ha anulado __eq__, el resultado es b.__eq__(a).
  • Si ninguno de los anteriores es el caso, Python repite el proceso buscando __cmp__. Si existe, los objetos son iguales si regresa zero.
  • Como respaldo final, Python llama object.__eq__(a, b), que es Trueiff ay bson el mismo objeto.

Si alguno de los métodos especiales regresa NotImplemented, Python actúa como si el método no existiera.

Tenga en cuenta ese último paso con cuidado: si ni ani se bsobrecarga ==, entonces a == bes lo mismo que a is b.


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

kev
fuente
1
Uhh, parece que los documentos de Python 3 eran incorrectos. Consulte bugs.python.org/issue4395 y el parche para una aclaración. TLDR: la subclase aún se compara primero, incluso si está en el lado derecho.
máximo
Hola kev, buen post. ¿Podría explicar dónde está documentado el primer punto y por qué está diseñado así?
wim
1
Sí, ¿dónde está documentado esto para Python 2? ¿Es una PEP?
Mr_and_Mrs_D
Basado en esta respuesta y los comentarios acompañados, esto me dejó más confundido que antes.
Sajuuk
y por cierto, ¿definir un método enlazado __eq__ solo en la instancia de algún tipo no es suficiente para que == se anule?
Sajuuk
5

Estoy escribiendo una respuesta actualizada para Python 3 a esta pregunta.

¿Cómo se __eq__maneja en Python y en qué orden?

a == b

Generalmente se entiende, pero no siempre el caso, que a == binvoca a.__eq__(b), otype(a).__eq__(a, b) .

Explícitamente, el orden de evaluación es:

  1. si bel tipo de es una subclase estricta (no el mismo tipo) de atipo de y tiene __eq__, llámelo y devuelva el valor si la comparación está implementada
  2. de lo contrario, si atiene __eq__, llámelo y devuélvalo si se implementa la comparación,
  3. de lo contrario, vea si no llamamos b __eq__y lo tiene, luego llámelo y devuélvalo si la comparación está implementada,
  4. de lo contrario, finalmente, haga la comparación de identidad, la misma comparación que is.

Sabemos si no se implementa una comparación si el método regresa NotImplemented.

(En Python 2, __cmp__se buscó un método, pero quedó obsoleto y se eliminó en Python 3).

Probemos el comportamiento de la primera verificación por nosotros mismos dejando que B sea subclase A, lo que muestra que la respuesta aceptada es incorrecta en este aspecto:

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

que solo imprime B __eq__ calledantes de regresar False.

¿Cómo conocemos este algoritmo completo?

Las otras respuestas aquí parecen incompletas y desactualizadas, así que actualizaré la información y le mostraré cómo podría buscar esto por sí mismo.

Esto se maneja en el nivel C.

Necesitamos mirar dos bits de código diferentes aquí: el predeterminado __eq__para objetos de clase objecty el código que busca y llama al __eq__método independientemente de si usa el predeterminado __eq__o uno personalizado.

Defecto __eq__

Al __eq__buscar en los documentos relevantes de la api de C , se nos muestra que __eq__es manejado por tp_richcompare, que en la "object"definición de tipo cpython/Objects/typeobject.cse define en object_richcomparefor 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;

Entonces aquí, si self == otherregresamos True, de lo contrario devolvemos el NotImplementedobjeto. Este es el comportamiento predeterminado para cualquier subclase de objeto que no implemente su propio __eq__método.

Como __eq__se llama

Luego encontramos los documentos de la API C, PyObject_RichCompare función , que llama do_richcompare.

Luego vemos que la tp_richcomparefunción, creada para el"object" definición de C, es llamada por do_richcompare, así que veamos eso un poco más de cerca.

La primera verificación en esta función es para las condiciones de los objetos que se comparan:

  • son no del mismo tipo, pero
  • el tipo del segundo es una subclase del tipo del primero, y
  • el tipo del segundo tiene un __eq__método,

luego llame al método del otro con los argumentos intercambiados, devolviendo el valor si se implementa. Si ese método no se implementa, continuamos ...

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

A continuación, vemos si podemos buscar el __eq__método del primer tipo y llamarlo. Siempre que el resultado no sea NotImplemented, es decir, esté implementado, lo devolvemos.

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

De lo contrario, si no probamos el método del otro tipo y está ahí, lo probamos y, si se implementa la comparación, lo devolvemos.

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

Finalmente, obtenemos una alternativa en caso de que no se implemente para ninguno de los dos tipos.

El respaldo verifica la identidad del objeto, es decir, si es el mismo objeto en el mismo lugar en la memoria; esta es la misma verificación que para 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;

Conclusión

En una comparación, respetamos primero la implementación de la subclase de la comparación.

Luego intentamos la comparación con la implementación del primer objeto, luego con la del segundo si no fue llamado.

Finalmente, usamos una prueba de identidad para comparar la igualdad.

Aaron Hall
fuente