Podczas pisania klas niestandardowych często ważne jest, aby umożliwić równoważność za pomocą operatorów ==
i !=
. W Pythonie jest to możliwe dzięki zastosowaniu odpowiednio metod specjalnych __eq__
i __ne__
. Najłatwiejszym sposobem, aby to zrobić, jest następująca metoda:
class Foo:
def __init__(self, item):
self.item = item
def __eq__(self, other):
if isinstance(other, self.__class__):
return self.__dict__ == other.__dict__
else:
return False
def __ne__(self, other):
return not self.__eq__(other)
Czy znasz bardziej elegancki sposób na zrobienie tego? Czy znasz jakieś szczególne wady korzystania z powyższej metody porównywania __dict__
?
Uwaga : Trochę wyjaśnienia - kiedy __eq__
i __ne__
są niezdefiniowane, znajdziesz to zachowanie:
>>> a = Foo(1)
>>> b = Foo(1)
>>> a is b
False
>>> a == b
False
Oznacza to, że a == b
ocenia, False
ponieważ tak naprawdę działa a is b
, test tożsamości (tj. „Czy a
ten sam obiekt jak b
?”).
Kiedy __eq__
i __ne__
są zdefiniowane, znajdziesz to zachowanie (którego właśnie szukamy):
>>> a = Foo(1)
>>> b = Foo(1)
>>> a is b
False
>>> a == b
True
python
equality
equivalence
gotgenes
źródło
źródło
is
operator odróżniający tożsamość obiektu od porównywania wartości.Odpowiedzi:
Rozważ ten prosty problem:
Tak więc Python domyślnie używa identyfikatorów obiektów do operacji porównania:
Przesłonięcie
__eq__
funkcji wydaje się rozwiązać problem:W Pythonie 2 zawsze pamiętaj o zastąpieniu
__ne__
funkcji, ponieważ dokumentacja stwierdza:W Pythonie 3 nie jest to już konieczne, ponieważ dokumentacja stwierdza:
Ale to nie rozwiązuje wszystkich naszych problemów. Dodajmy podklasę:
Uwaga: Python 2 ma dwa rodzaje klas:
w stylu klasycznym (lub starym stylu ) klas, które mają nie dziedziczyć z
object
i które zostały zadeklarowane jakoclass A:
,class A():
lubclass A(B):
gdzieB
jest klasa stylu klasycznym;klasy w nowym stylu , które dziedziczą
object
i są zadeklarowane jakoclass A(object)
lubclass A(B):
gdzieB
jest klasa w nowym stylu. Python 3 ma tylko klasy w nowym stylu, które są zadeklarowane jakoclass A:
,class A(object):
lubclass A(B):
.W przypadku klas klasycznych operacja porównania zawsze wywołuje metodę pierwszego operandu, podczas gdy w klasach nowego stylu zawsze wywołuje metodę operandu podklasy, niezależnie od kolejności operandów .
A więc, jeśli
Number
jest to klasa w stylu klasycznym:n1 == n3
połączenian1.__eq__
;n3 == n1
połączenian3.__eq__
;n1 != n3
połączenian1.__ne__
;n3 != n1
połączenian3.__ne__
.A jeśli
Number
jest klasą w nowym stylu:n1 == n3
in3 == n1
wezwanien3.__eq__
;n1 != n3
in3 != n1
zadzwonićn3.__ne__
.Aby rozwiązać problem nieprzemienności operatorów
==
i!=
dla klas klasycznych języka Python 2, metody__eq__
i__ne__
powinny zwracaćNotImplemented
wartość, gdy typ argumentu nie jest obsługiwany. Dokumentacja definiujeNotImplemented
wartość jako:W tym przypadku delegaci operator operacja porównaniu do odzwierciedlenie metody z innego argumentu. W dokumentacji definiuje odzwierciedlenie metody jak:
Wynik wygląda następująco:
Zwracanie
NotImplemented
wartości zamiastFalse
jest słuszne, nawet dla klas w nowym stylu jeśli przemienność z==
i!=
jest pożądane operatorów, gdy argumenty są niepowiązanych typów (bez spadku).Czy już dotarliśmy? Nie do końca. Ile mamy unikalnych numerów?
Zestawy używają skrótów obiektów, a domyślnie Python zwraca skrót identyfikatora obiektu. Spróbujmy to zastąpić:
Wynik końcowy wygląda następująco (dodałem na końcu kilka stwierdzeń do weryfikacji):
źródło
hash(tuple(sorted(self.__dict__.items())))
nie będzie działać, jeśli wśród wartości obiektu znajdują się obiekty, których nie da się ukryćself.__dict__
(tzn. jeśli którykolwiek z atrybutów obiektu jest ustawiony na, powiedzmy, alist
).__ne__
za pomocą==
zamiast__eq__
.__ne__
już implementować : „Domyślnie__ne__()
deleguje__eq__()
i odwraca wynik, chyba że jestNotImplemented
”. 2. Jeśli ktoś nadal chce wdrożyć__ne__
, bardziej rodzajowe realizacja (jeden używany przez Pythonie 3 myślę) wynosi:x = self.__eq__(other); if x is NotImplemented: return x; else: return not x
. 3. Podane__eq__
i__ne__
implementacje są nieoptymalne:if isinstance(other, type(self)):
daje 22__eq__
i 10__ne__
wywołań, aif isinstance(self, type(other)):
dawałoby 16__eq__
i 6__ne__
wywołań.Trzeba zachować ostrożność przy dziedziczeniu:
Dokładniej sprawdź typy, takie jak to:
Poza tym twoje podejście będzie działać dobrze, po to są specjalne metody.
źródło
NotImplemented
zgodnie z sugestią zawsze spowodujesuperclass.__eq__(subclass)
pożądane zachowanie.if other is self
. Pozwala to uniknąć dłuższego porównywania słowników i może być ogromną oszczędnością, gdy obiekty są używane jako klucze słowników.__hash__()
Opisujesz sposób, w jaki zawsze to robiłem. Ponieważ jest on całkowicie ogólny, zawsze możesz podzielić tę funkcjonalność na klasę mixin i odziedziczyć ją w klasach, w których chcesz tę funkcjonalność.
źródło
other
należy do podklasyself.__class__
.__dict__
porównaniem jest to, że masz atrybut, którego nie chcesz brać pod uwagę w definicji równości (na przykład unikalny identyfikator obiektu lub metadane, takie jak znacznik utworzony w czasie).Nie była to bezpośrednia odpowiedź, ale wydawała się na tyle istotna, że można się nią zająć, ponieważ czasami oszczędza to trochę nudnego nudy. Wytnij prosto z dokumentów ...
funkools.total_ordering (cls)
Biorąc pod uwagę klasę definiującą jedną lub więcej bogatych metod porządkowania porównań, ten dekorator klas dostarcza resztę. Upraszcza to wysiłek związany z określeniem wszystkich możliwych bogatych operacji porównania:
Klasa musi określić jeden
__lt__()
,__le__()
,__gt__()
, lub__ge__()
. Ponadto klasa powinna podać__eq__()
metodę.Nowości w wersji 2.7
źródło
Nie musisz zastępować obu
__eq__
i__ne__
możesz tylko zastąpić,__cmp__
ale będzie to miało wpływ na wynik ==,! ==, <,> i tak dalej.is
testy tożsamości obiektu. Oznacza to, żeis
b będzie miało miejsceTrue
w przypadku, gdy oba a i b utrzymują odniesienie do tego samego obiektu. W Pythonie zawsze przechowujesz odwołanie do obiektu w zmiennej, a nie do rzeczywistego obiektu, więc w gruncie rzeczy a jest prawdą b, obiekty w nich powinny znajdować się w tym samym miejscu w pamięci. W jaki sposób i co najważniejsze, dlaczego miałby pan obchodzić to zachowanie?Edycja: Nie wiedziałem, że
__cmp__
został usunięty z Pythona 3, więc unikaj go.źródło
Z tej odpowiedzi: https://stackoverflow.com/a/30676267/541136 Udowodniłem to, chociaż poprawne jest definiowanie
__ne__
w kategoriach__eq__
- zamiastpowinieneś użyć:
źródło
Myślę, że dwa warunki, których szukasz, to równość (==) i tożsamość (jest). Na przykład:
źródło
Test „jest” sprawdzi tożsamość za pomocą wbudowanej funkcji „id ()”, która zasadniczo zwraca adres pamięci obiektu, a zatem nie jest przeciążalna.
Jednak w przypadku testowania równości klasy prawdopodobnie chcesz być nieco bardziej rygorystyczny w stosunku do swoich testów i porównywać tylko atrybuty danych w swojej klasie:
Ten kod będzie porównywał tylko niefunkcjonalne dane członków twojej klasy, a także pomija wszystko prywatne, co jest generalnie tym, czego chcesz. W przypadku Plain Old Python Objects mam klasę bazową, która implementuje __init__, __str__, __repr__ i __eq__, więc moje obiekty POPO nie przenoszą ciężaru całej tej dodatkowej (i w większości przypadków identycznej) logiki.
źródło
__eq__
zostanie zadeklarowane wCommonEqualityMixin
(patrz inna odpowiedź). Uważam to za szczególnie przydatne podczas porównywania instancji klas pochodzących z Base w SQLAlchemy. Aby nie porównywać_sa_instance_state
, zmieniłemkey.startswith("__")):
nakey.startswith("_")):
. Miałem też w sobie pewne referencje, a odpowiedź Algoriasa generowała nieskończoną rekurencję. Wymieniłem'_'
więc wszystkie odwołania wsteczne na początku , aby były one pomijane podczas porównywania. UWAGA: w Python 3.x zmieńiteritems()
naitems()
.__dict__
instancja nie ma niczego, co zaczyna się od__
niej, chyba że zostało zdefiniowane przez użytkownika. Rzeczy takie jak__class__
,__init__
itp nie są w instancji__dict__
, ale w swojej klasie__dict__
. OTOH, prywatne atrybuty można łatwo zacząć__
i prawdopodobnie powinny być używane__eq__
. Czy możesz wyjaśnić, czego dokładnie próbowałeś uniknąć, pomijając__
atrybuty z prefiksem?Zamiast używać subclassing / mixins, lubię używać ogólnego dekoratora klas
Stosowanie:
źródło
Obejmuje to komentarze do odpowiedzi Algoriasa i porównuje obiekty według jednego atrybutu, ponieważ nie dbam o cały dyktando.
hasattr(other, "id")
musi być prawdą, ale wiem, że dzieje się tak, ponieważ ustawiłem to w konstruktorze.źródło