Ponieważ Python nie udostępnia lewej / prawej wersji swoich operatorów porównania, w jaki sposób decyduje, którą funkcję wywołać?
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
To wydaje się wywoływać obie __eq__
funkcje.
Szukam oficjalnego drzewa decyzyjnego.
źródło
__eq__
tylko na wystąpieniu jakiegoś typu nie jest wystarczające do zastąpienia ==?Piszę zaktualizowaną odpowiedź dla Pythona 3 na to pytanie.
Powszechnie wiadomo, ale nie zawsze tak jest, że
a == b
wywołujea.__eq__(b)
lubtype(a).__eq__(a, b)
.Mówiąc wprost, kolejność oceny jest następująca:
b
typ jest ścisłą podklasą (nie tym samym typem) tegoa
typu i ma__eq__
znak, wywołaj go i zwróć wartość, jeśli porównanie jest zaimplementowane,a
ma__eq__
, wywołaj go i zwróć, jeśli porównanie jest zaimplementowane,__eq__
i ma to, a następnie wywołaj i zwróć, jeśli porównanie jest zaimplementowane,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 powrotemFalse
.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 klasyobject
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 przeztp_richcompare
- które w"object"
definicji typu wcpython/Objects/typeobject.c
jest zdefiniowane wobject_richcompare
forcase 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;
Więc tutaj, jeśli
self == other
wrócimyTrue
, w przeciwnym razie zwrócimyNotImplemented
obiekt. Jest to domyślne zachowanie dla dowolnej podklasy obiektu, która nie implementuje własnej__eq__
metody.Jak
__eq__
się nazywaNastę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 przezdo_richcompare
, więc przyjrzyjmy się temu trochę dokładniej.Pierwsza kontrola w tej funkcji dotyczy warunków porównywanych obiektów:
__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.
źródło