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 == b
wywołuje a.__eq__(b)
lub type(a).__eq__(a, b)
.
Mówiąc wprost, kolejność oceny jest następująca:
- jeśli
b
typ jest ścisłą podklasą (nie tym samym typem) tego a
typu i ma __eq__
znak, wywołaj go i zwróć wartość, jeśli porównanie jest zaimplementowane,
- w przeciwnym razie, jeśli
a
ma __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__ called
przed 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 object
oraz 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.c
jest zdefiniowane w 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
res = (self == other) ? Py_True : Py_NotImplemented;
Py_INCREF(res);
break;
Więc tutaj, jeśli self == other
wrócimy True
, w przeciwnym razie zwrócimy NotImplemented
obiekt. 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_richcompare
funkcja 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.