Come viene gestito __eq__ in Python e in quale ordine?


96

Poiché Python non fornisce le versioni sinistra / destra dei suoi operatori di confronto, come decide quale funzione chiamare?

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

Questo sembra chiamare entrambe le __eq__funzioni.

Sto cercando l'albero decisionale ufficiale.

Risposte:


119

L' a == bespressione invoca A.__eq__, poiché esiste. Il suo codice include self.value == other. Poiché gli int non sanno come confrontarsi con i B, Python prova a invocare B.__eq__per vedere se sa come confrontarsi con un int.

Se modifichi il codice per mostrare quali valori vengono confrontati:

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

stamperà:

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

69

Quando Python2.x vede a == b, prova quanto segue.

  • Se type(b)è una classe di nuovo stile ed type(b)è una sottoclasse di type(a)e type(b)ha sovrascritto __eq__, il risultato è b.__eq__(a).
  • Se type(a)ha sovrascritto __eq__(cioè type(a).__eq__non lo è object.__eq__), il risultato è a.__eq__(b).
  • Se type(b)ha sovrascritto __eq__, il risultato è b.__eq__(a).
  • Se nessuna delle precedenti è il caso, Python ripete il processo cercando __cmp__. Se esiste, gli oggetti sono uguali se e solo se ritorna zero.
  • Come ripiego finale, Python chiama object.__eq__(a, b), che è Trueiff ae bsono lo stesso oggetto.

Se uno dei metodi speciali ritorna NotImplemented, Python si comporta come se il metodo non esistesse.

Notare attentamente l'ultimo passaggio: se né ané si bsovraccarica ==, allora a == bè lo stesso di a is b.


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


1
Uhh, sembra che i documenti di Python 3 non fossero corretti. Vedi bugs.python.org/issue4395 e la patch per chiarimenti. TLDR: sottoclasse ancora confrontata per prima, anche se è sulla destra.
max

Ciao kev, bel post. Puoi spiegare dove è documentato il primo punto elenco e perché è progettato in questo modo?
wim

1
Sì, dove è documentato per Python 2? È un PEP?
Mr_and_Mrs_D

basandomi su questa risposta e sui commenti accompagnati, questo mi ha lasciato più confuso di prima.
Sajuuk

e btw, definire un metodo vincolato __eq__ solo sull'istanza di qualche tipo non è sufficiente per sovrascrivere ==?
Sajuuk

3

Sto scrivendo una risposta aggiornata per Python 3 a questa domanda.

Come viene __eq__gestito in Python e in quale ordine?

a == b

È generalmente compreso, ma non sempre è così, che a == binvoca a.__eq__(b), o type(a).__eq__(a, b).

Esplicitamente, l'ordine di valutazione è:

  1. se bil tipo di è una sottoclasse rigorosa (non lo stesso tipo) del atipo di se ha un __eq__, chiamalo e restituisci il valore se il confronto è implementato,
  2. altrimenti, se aha __eq__, chiamalo e restituiscilo se il confronto è implementato,
  3. altrimenti, vedi se non abbiamo chiamato b __eq__e ce l' ha, quindi chiamalo e restituiscilo se il confronto è implementato,
  4. altrimenti, infine, fai il confronto per l'identità, lo stesso confronto di is.

Sappiamo se un confronto non viene implementato se il metodo restituisce NotImplemented.

(In Python 2, c'era un __cmp__metodo che è stato cercato, ma è stato deprecato e rimosso in Python 3.)

Testiamo il comportamento del primo controllo per noi stessi lasciando che B sottoclasse A, il che mostra che la risposta accettata è sbagliata su questo conteggio:

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

che stampa solo B __eq__ calledprima della restituzione False.

Come conosciamo questo algoritmo completo?

Le altre risposte qui sembrano incomplete e non aggiornate, quindi aggiornerò le informazioni e ti mostrerò come potresti cercarle da solo.

Questo è gestito a livello C.

Abbiamo bisogno di esaminare due diversi bit di codice qui: il valore predefinito __eq__per gli oggetti di classe objecte il codice che cerca e chiama il __eq__metodo indipendentemente dal fatto che utilizzi quello predefinito __eq__o personalizzato.

Predefinito __eq__

Cercare __eq__nei documenti C api pertinenti ci mostra che __eq__è gestito da tp_richcompare- che nella "object"definizione del tipo in cpython/Objects/typeobject.cè definito in 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;

Quindi qui, se self == othertorniamo True, altrimenti restituiamo l' NotImplementedoggetto. Questo è il comportamento predefinito per qualsiasi sottoclasse di oggetto che non implementa il proprio __eq__metodo.

Come __eq__viene chiamato

Quindi troviamo i documenti dell'API C, la funzione PyObject_RichCompare , che chiama do_richcompare.

Quindi vediamo che la tp_richcomparefunzione, creata per la "object"definizione C è chiamata da do_richcompare, quindi guardiamola un po 'più da vicino.

Il primo controllo in questa funzione è per le condizioni degli oggetti confrontati:

  • non sono dello stesso tipo, ma
  • il tipo del secondo è una sottoclasse del tipo del primo, e
  • il secondo tipo ha un __eq__metodo,

quindi chiama il metodo dell'altro con gli argomenti scambiati, restituendo il valore se implementato. Se questo metodo non è implementato, continuiamo ...

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

Successivamente vediamo se possiamo cercare il __eq__metodo dal primo tipo e chiamarlo. Finché il risultato non è NotImplemented, ovvero è implementato, lo restituiamo.

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

Altrimenti, se non abbiamo provato il metodo dell'altro tipo ed è lì, lo proviamo e se il confronto è implementato, lo restituiamo.

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

Infine, otteniamo un fallback nel caso in cui non sia implementato per nessuno dei due tipi.

Il fallback controlla l'identità dell'oggetto, cioè se è lo stesso oggetto nello stesso punto della memoria - questo è lo stesso controllo di 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;

Conclusione

In un confronto, rispettiamo prima l'implementazione della sottoclasse del confronto.

Quindi tentiamo il confronto con l'implementazione del primo oggetto, quindi con il secondo se non è stato chiamato.

Infine usiamo un test per l'identità per il confronto per l'uguaglianza.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.