Rozważ ten prosty problem:
class Number:
def __init__(self, number):
self.number = number
n1 = Number(1)
n2 = Number(1)
n1 == n2 # False -- oops
Tak więc Python domyślnie używa identyfikatorów obiektów do operacji porównania:
id(n1) # 140400634555856
id(n2) # 140400634555920
Przesłonięcie __eq__
funkcji wydaje się rozwiązać problem:
def __eq__(self, other):
"""Overrides the default implementation"""
if isinstance(other, Number):
return self.number == other.number
return False
n1 == n2 # True
n1 != n2 # True in Python 2 -- oops, False in Python 3
W Pythonie 2 zawsze pamiętaj o zastąpieniu __ne__
funkcji, ponieważ dokumentacja stwierdza:
Nie ma żadnych domniemanych relacji między operatorami porównania. Prawda x==y
nie sugeruje, że x!=y
jest to fałsz. W związku z tym podczas definiowania __eq__()
należy również zdefiniować, __ne__()
aby operatorzy zachowywali się zgodnie z oczekiwaniami.
def __ne__(self, other):
"""Overrides the default implementation (unnecessary in Python 3)"""
return not self.__eq__(other)
n1 == n2 # True
n1 != n2 # False
W Pythonie 3 nie jest to już konieczne, ponieważ dokumentacja stwierdza:
Domyślnie __ne__()
deleguje __eq__()
i odwraca wynik, chyba że tak jest NotImplemented
. Nie ma innych domniemanych związków między operatorami porównania, na przykład prawda (x<y or x==y)
nie implikuje x<=y
.
Ale to nie rozwiązuje wszystkich naszych problemów. Dodajmy podklasę:
class SubNumber(Number):
pass
n3 = SubNumber(1)
n1 == n3 # False for classic-style classes -- oops, True for new-style classes
n3 == n1 # True
n1 != n3 # True for classic-style classes -- oops, False for new-style classes
n3 != n1 # False
Uwaga: Python 2 ma dwa rodzaje klas:
w stylu klasycznym (lub starym stylu ) klas, które mają nie dziedziczyć zobject
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łączenia n1.__eq__
;
n3 == n1
połączenia n3.__eq__
;
n1 != n3
połączenia n1.__ne__
;
n3 != n1
połączenia n3.__ne__
.
A jeśli Number
jest klasą w nowym stylu:
- zarówno
n1 == n3
i n3 == n1
wezwanie n3.__eq__
;
- zarówno
n1 != n3
i n3 != 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 definiuje NotImplemented
wartość jako:
Metody numeryczne i metody porównywania rozszerzonego mogą zwrócić tę wartość, jeśli nie implementują operacji dla podanych argumentów. (Tłumacz interpretuje następnie operację odbicia lub inną operację zastępczą, w zależności od operatora). Jej wartość prawdy jest prawdziwa.
W tym przypadku delegaci operator operacja porównaniu do odzwierciedlenie metody z innego argumentu. W dokumentacji definiuje odzwierciedlenie metody jak:
Nie ma wersji tych metod wymiany argumentów (do użycia, gdy lewy argument nie obsługuje operacji, ale prawy argument tak;); raczej __lt__()
i __gt__()
są wzajemnym odbiciem, __le__()
i __ge__()
są wzajemnym odbiciem,
__eq__()
i __ne__()
są ich własnym odbiciem.
Wynik wygląda następująco:
def __eq__(self, other):
"""Overrides the default implementation"""
if isinstance(other, Number):
return self.number == other.number
return NotImplemented
def __ne__(self, other):
"""Overrides the default implementation (unnecessary in Python 3)"""
x = self.__eq__(other)
if x is NotImplemented:
return NotImplemented
return not x
Zwracanie NotImplemented
wartości zamiast False
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?
len(set([n1, n2, n3])) # 3 -- oops
Zestawy używają skrótów obiektów, a domyślnie Python zwraca skrót identyfikatora obiektu. Spróbujmy to zastąpić:
def __hash__(self):
"""Overrides the default implementation"""
return hash(tuple(sorted(self.__dict__.items())))
len(set([n1, n2, n3])) # 1
Wynik końcowy wygląda następująco (dodałem na końcu kilka stwierdzeń do weryfikacji):
class Number:
def __init__(self, number):
self.number = number
def __eq__(self, other):
"""Overrides the default implementation"""
if isinstance(other, Number):
return self.number == other.number
return NotImplemented
def __ne__(self, other):
"""Overrides the default implementation (unnecessary in Python 3)"""
x = self.__eq__(other)
if x is not NotImplemented:
return not x
return NotImplemented
def __hash__(self):
"""Overrides the default implementation"""
return hash(tuple(sorted(self.__dict__.items())))
class SubNumber(Number):
pass
n1 = Number(1)
n2 = Number(1)
n3 = SubNumber(1)
n4 = SubNumber(4)
assert n1 == n2
assert n2 == n1
assert not n1 != n2
assert not n2 != n1
assert n1 == n3
assert n3 == n1
assert not n1 != n3
assert not n3 != n1
assert not n1 == n4
assert not n4 == n1
assert n1 != n4
assert n4 != n1
assert len(set([n1, n2, n3, ])) == 1
assert len(set([n1, n2, n3, n4])) == 2
is
operator odróżniający tożsamość obiektu od porównywania wartości.