Jest to doskonały przykład na to, dlaczego __dunder__
metod nie należy używać bezpośrednio, ponieważ często nie stanowią one odpowiednich zamienników dla ich równoważnych operatorów; ==
zamiast tego powinieneś używać operatora do porównań równości lub w tym szczególnym przypadku, podczas sprawdzania None
, użyj is
(przejdź do końca odpowiedzi, aby uzyskać więcej informacji).
Zrobiłeś
None.__eq__('a')
# NotImplemented
Który zwraca, NotImplemented
ponieważ porównywane typy są różne. Rozważmy inny przykład, w którym dwa obiekty o różnych typach są porównywane w ten sposób, na przykład 1
i 'a'
. Postępowanie (1).__eq__('a')
również nie jest poprawne i wróci NotImplemented
. Właściwym sposobem porównania tych dwóch wartości dla równości byłby
1 == 'a'
# False
To, co się tutaj dzieje, jest
- Najpierw
(1).__eq__('a')
próbowano, co wraca NotImplemented
. Oznacza to, że operacja nie jest obsługiwana, więc
'a'.__eq__(1)
jest wywoływana, co również zwraca to samo NotImplemented
. Więc,
- Obiekty są traktowane tak, jakby nie były takie same i
False
są zwracane.
Oto ładny mały MCVE wykorzystujący kilka niestandardowych klas, aby zilustrować, jak to się dzieje:
class A:
def __eq__(self, other):
print('A.__eq__')
return NotImplemented
class B:
def __eq__(self, other):
print('B.__eq__')
return NotImplemented
class C:
def __eq__(self, other):
print('C.__eq__')
return True
a = A()
b = B()
c = C()
print(a == b)
# A.__eq__
# B.__eq__
# False
print(a == c)
# A.__eq__
# C.__eq__
# True
print(c == a)
# C.__eq__
# True
Oczywiście to nie wyjaśnia, dlaczego operacja zwraca prawdę. Dzieje się tak, ponieważ NotImplemented
jest to prawdziwa wartość:
bool(None.__eq__("a"))
# True
Taki sam jak,
bool(NotImplemented)
# True
Aby uzyskać więcej informacji na temat tego, jakie wartości są uważane za prawdziwe, a jakie fałszywe, zobacz sekcję z dokumentacją dotyczącą testowania wartości prawdy , a także tę odpowiedź . Warto w tym miejscu zauważyć, że NotImplemented
to prawda, ale byłaby to inna historia, gdyby klasa zdefiniowała metodę __bool__
lub __len__
, która zwraca False
lub 0
odpowiednio.
Jeśli chcesz funkcjonalnego odpowiednika ==
operatora, użyj operator.eq
:
import operator
operator.eq(1, 'a')
# False
Jednak, jak wspomniano wcześniej, w tym konkretnym scenariuszu , w którym szukasz None
, użyj is
:
var = 'a'
var is None
# False
var2 = None
var2 is None
# True
Funkcjonalnym odpowiednikiem tego jest użycie operator.is_
:
operator.is_(var2, None)
# True
None
jest obiektem specjalnym iw dowolnym momencie w pamięci istnieje tylko 1 wersja. IOW, jest to jedyny singleton w NoneType
klasie (ale ten sam obiekt może mieć dowolną liczbę odniesień). Te wytyczne PEP8 zrobić to wyraźne:
Porównania do singletonów, takich jak, None
powinny być zawsze wykonywane z operatorami równości is
lub
is not
nigdy.
Podsumowując, w przypadku singletonów, takich jak None
sprawdzenie referencji, is
jest bardziej odpowiednie, chociaż oba ==
i is
będą działać dobrze.