Piszę zaktualizowaną odpowiedź dla Pythona 3 na to pytanie.
Jak jest __eq__obsługiwane w Pythonie i w jakiej kolejności?
a == b
Powszechnie wiadomo, ale nie zawsze tak jest, że a == bwywołuje a.__eq__(b)lub type(a).__eq__(a, b).
Mówiąc wprost, kolejność oceny jest następująca:
- jeśli
btyp jest ścisłą podklasą (nie tym samym typem) tego atypu i ma __eq__znak, wywołaj go i zwróć wartość, jeśli porównanie jest zaimplementowane,
- w przeciwnym razie, jeśli
ama __eq__, wywołaj go i zwróć, jeśli porównanie jest zaimplementowane,
- inaczej, zobacz, czy nie wywołaliśmy b
__eq__i ma to, a następnie wywołaj i zwróć, jeśli porównanie jest zaimplementowane,
- w przeciwnym razie wykonaj porównanie tożsamości, to samo porównanie co
is.
Wiemy, czy porównanie nie jest zaimplementowane, jeśli metoda zwraca NotImplemented.
(W Pythonie 2 była __cmp__metoda, której szukano, ale została wycofana i usunięta w Pythonie 3.)
Przetestujmy dla siebie zachowanie pierwszego sprawdzenia, pozwalając na podklasę B A, która pokazuje, że zaakceptowana odpowiedź jest błędna pod tym względem:
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
które drukuje tylko B __eq__ calledprzed powrotem False.
Skąd znamy ten pełny algorytm?
Inne odpowiedzi tutaj wydają się niekompletne i nieaktualne, więc zaktualizuję informacje i pokażę, jak możesz to sprawdzić.
Jest to obsługiwane na poziomie C.
Musimy przyjrzeć się tutaj dwóm różnym bitom kodu - domyślnemu __eq__dla obiektów klasy objectoraz kodowi, który wyszukuje i wywołuje __eq__metodę niezależnie od tego, czy używa domyślnej, __eq__czy niestandardowej.
Domyślna __eq__
Wyszukiwanie __eq__w odpowiednich dokumentach C api pokazuje, że __eq__jest obsługiwane przez tp_richcompare- które w "object"definicji typu w cpython/Objects/typeobject.cjest zdefiniowane w 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
res = (self == other) ? Py_True : Py_NotImplemented;
Py_INCREF(res);
break;
Więc tutaj, jeśli self == otherwrócimy True, w przeciwnym razie zwrócimy NotImplementedobiekt. Jest to domyślne zachowanie dla dowolnej podklasy obiektu, która nie implementuje własnej __eq__metody.
Jak __eq__się nazywa
Następnie znajdujemy dokumentację C API, funkcję PyObject_RichCompare , która wywołuje do_richcompare.
Następnie widzimy, że tp_richcomparefunkcja utworzona dla "object"definicji C jest wywoływana przez do_richcompare, więc przyjrzyjmy się temu trochę dokładniej.
Pierwsza kontrola w tej funkcji dotyczy warunków porównywanych obiektów:
- nie są tego samego typu, ale
- drugi typ jest podklasą pierwszego typu, a
- drugi typ ma
__eq__metodę,
następnie wywołaj metodę drugiej osoby z zamienionymi argumentami, zwracając wartość, jeśli jest zaimplementowana. Jeśli ta metoda nie zostanie zaimplementowana, kontynuujemy ...
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);
Następnie sprawdzamy, czy możemy wyszukać __eq__metodę z pierwszego typu i wywołać ją. Dopóki wynik nie jest NotImplemented, czyli jest zaimplementowany, zwracamy go.
if ((f = Py_TYPE(v)->tp_richcompare) != NULL) {
res = (*f)(v, w, op);
if (res != Py_NotImplemented)
return res;
Py_DECREF(res);
W przeciwnym razie, jeśli nie wypróbowaliśmy metody innego typu, a ona jest, wtedy ją wypróbowujemy, a jeśli porównanie jest zaimplementowane, zwracamy je.
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);
}
Na koniec otrzymujemy rezerwę na wypadek, gdyby nie została zaimplementowana dla żadnego typu.
Fallback sprawdza tożsamość obiektu, czyli czy jest to ten sam obiekt w tym samym miejscu w pamięci - to jest to samo sprawdzenie co dla 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;
Wniosek
W porównaniu najpierw szanujemy implementację porównania podklas.
Następnie próbujemy porównać z implementacją pierwszego obiektu, a następnie z implementacją drugiego, jeśli nie został wywołany.
Na koniec używamy testu tożsamości do porównania równości.