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 == b
invoca a.__eq__(b)
, o type(a).__eq__(a, b)
.
Esplicitamente, l'ordine di valutazione è:
- se
b
il tipo di è una sottoclasse rigorosa (non lo stesso tipo) del a
tipo di se ha un __eq__
, chiamalo e restituisci il valore se il confronto è implementato,
- altrimenti, se
a
ha __eq__
, chiamalo e restituiscilo se il confronto è implementato,
- altrimenti, vedi se non abbiamo chiamato b
__eq__
e ce l' ha, quindi chiamalo e restituiscilo se il confronto è implementato,
- 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__ called
prima 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 object
e 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_richcompare
for 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 == other
torniamo True
, altrimenti restituiamo l' NotImplemented
oggetto. 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_richcompare
funzione, 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.