Como __eq__ é tratado em Python e em que ordem?

96

Visto que o Python não fornece versões esquerda / direita de seus operadores de comparação, como ele decide qual função chamar?

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

Isso parece chamar ambas as __eq__funções.

Estou procurando a árvore de decisão oficial.

PyProg
fonte

Respostas:

119

A a == bexpressão invoca A.__eq__, uma vez que existe. Seu código inclui self.value == other. Como os int's não sabem como se comparar aos B's, o Python tenta invocar B.__eq__para ver se sabe como se comparar a um int.

Se você alterar seu código para mostrar quais valores estão sendo comparados:

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

vai 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
fonte
69

Quando Python2.x vê a == b, ele tenta o seguinte.

  • Se type(b)for uma classe de novo estilo e type(b)for uma subclasse de type(a)e type(b)tiver sido sobrescrito __eq__, o resultado será b.__eq__(a).
  • Se type(a)foi substituído __eq__(ou seja, type(a).__eq__não é object.__eq__), o resultado é a.__eq__(b).
  • Se type(b)foi substituído __eq__, o resultado é b.__eq__(a).
  • Se nenhum dos itens acima for o caso, o Python repete o processo procurando __cmp__. Se existir, os objetos são iguais se retornar zero.
  • Como fallback final, chamadas Python object.__eq__(a, b), que são Trueiff ae bsão o mesmo objeto.

Se algum dos métodos especiais retornar NotImplemented, o Python atuará como se o método não existisse.

Observe a última etapa com cuidado: se nem anem bsobrecarregar ==, então a == bé o mesmo que a is b.


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

kev
fonte
1
Uhh, parece que os documentos do python 3 estavam incorretos. Veja bugs.python.org/issue4395 e o patch para esclarecimentos. TLDR: a subclasse ainda é comparada primeiro, mesmo se estiver no rhs.
máx.
Oi kev, boa postagem. Você poderia explicar onde o primeiro marcador é documentado e por que foi projetado assim?
wim
1
Sim, onde isso está documentado para python 2? É um PEP?
Mr_and_Mrs_D
com base nesta resposta e comentários acompanhados, isso apenas me deixou mais confuso do que antes.
Sajuuk de
e btw, definir um método vinculado __eq__ apenas na instância de algum tipo não é suficiente para == ser sobrescrito?
Sajuuk
3

Estou escrevendo uma resposta atualizada para Python 3 a esta pergunta.

Como é __eq__tratado em Python e em que ordem?

a == b

É geralmente entendido, mas nem sempre o caso, que a == binvoca a.__eq__(b), ou type(a).__eq__(a, b).

Explicitamente, a ordem de avaliação é:

  1. if bé uma subclasse estrita (não é o mesmo tipo) do atipo de e tem um __eq__, chame-o e retorne o valor se a comparação for implementada,
  2. senão, se ativer __eq__, chame-o e retorne-o se a comparação for implementada,
  3. caso contrário, veja se não chamamos b's __eq__e ele tem, então chame e retorne se a comparação for implementada,
  4. senão, finalmente, faça a comparação para identidade, a mesma comparação que is.

Sabemos se uma comparação não é implementada se o método retornar NotImplemented.

(No Python 2, havia um __cmp__método que foi procurado, mas foi descontinuado e removido no Python 3.)

Vamos testar o comportamento da primeira verificação por nós mesmos, deixando B subclasse A, o que mostra que a resposta aceita está errada nesta contagem:

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 só imprime B __eq__ calledantes de retornar False.

Como sabemos esse algoritmo completo?

As outras respostas aqui parecem incompletas e desatualizadas, portanto, atualizarei as informações e mostrarei como você pode verificar isso por si mesmo.

Isso é tratado no nível C.

Precisamos examinar dois bits diferentes de código aqui - o padrão __eq__para objetos de classe objecte o código que procura e chama o __eq__método, independentemente de usar o padrão __eq__ou personalizado.

Padrão __eq__

Olhando __eq__-se nos docs relevantes API C mostra-nos que __eq__é tratado por tp_richcompare- que na "object"definição do tipo de cpython/Objects/typeobject.cé definida em object_richcomparepara 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;

Portanto, aqui, se self == otherretornarmos True, retornaremos o NotImplementedobjeto. Este é o comportamento padrão para qualquer subclasse de objeto que não implementa seu próprio __eq__método.

Como __eq__é chamado

Em seguida, encontramos os documentos da API C, a função PyObject_RichCompare , que chama do_richcompare.

Então, vemos que a tp_richcomparefunção criada para a "object"definição C é chamada por do_richcompare, então vamos examinar isso um pouco mais de perto.

A primeira verificação nesta função é para as condições dos objetos sendo comparados:

  • não são do mesmo tipo, mas
  • o tipo do segundo é uma subclasse do tipo do primeiro, e
  • o segundo tipo tem um __eq__método,

em seguida, chame o método do outro com os argumentos trocados, retornando o valor se implementado. Se esse método não for implementado, 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);

Em seguida, veremos se podemos pesquisar o __eq__método do primeiro tipo e chamá-lo. Desde que o resultado não seja NotImplemented, ou seja, ele seja implementado, nós o retornamos.

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

Do contrário, se não tentamos o método do outro tipo e ele está lá, então tentamos e, se a comparação for implementada, a retornamos.

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

Por fim, obtemos um fallback caso não seja implementado para nenhum dos tipos.

O substituto verifica a identidade do objeto, ou seja, se é o mesmo objeto no mesmo lugar na memória - esta é a mesma verificação de 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;

Conclusão

Em uma comparação, respeitamos a implementação da subclasse de comparação primeiro.

Em seguida, tentamos a comparação com a implementação do primeiro objeto e, em seguida, com a do segundo caso não tenha sido chamado.

Finalmente, usamos um teste de identidade para comparação de igualdade.

Aaron Hall
fonte