Dlaczego, jeśli nie ma. eq ("a")` wydaje się być prawdziwe (ale nie do końca)?
Jeśli wykonasz następującą instrukcję w Pythonie 3.7, to (z moich testów) wydrukuje b
:
if None.__eq__("a"):
print("b")
Jednak None.__eq__("a")
ocenia na NotImplemented
.
Naturalnie, "a".__eq__("a")
ocenia do True
, a "b".__eq__("a")
ocenia do False
.
Początkowo odkryłem to podczas testowania wartości zwracanej funkcji, ale nie zwróciłem niczego w drugim przypadku -- więc funkcja zwróciła None
.
4 answers
Jest to świetny przykład, dlaczego metody __dunder__
nie powinny być używane bezpośrednio, ponieważ często nie są odpowiednimi zamiennikami dla ich równoważnych operatorów; powinieneś użyć operatora ==
zamiast porównania równości, lub w tym szczególnym przypadku, podczas sprawdzania None
, użyj is
(przejdź do dołu odpowiedzi, aby uzyskać więcej informacji).
Zrobiłeś
None.__eq__('a')
# NotImplemented
Który zwraca NotImplemented
ponieważ porównywane typy są różne. Rozważ inny przykład gdzie w ten sposób porównywane są dwa obiekty o różnych typach, np. 1
i 'a'
. Robienie (1).__eq__('a')
również nie jest poprawne i zwróci NotImplemented
. Poprawnym sposobem porównania tych dwóch wartości dla równości byłoby
1 == 'a'
# False
To, co się tutaj dzieje, to
- pierwszy,
(1).__eq__('a')
jest wypróbowany, który zwracaNotImplemented
. Oznacza to, że operacja nie jest obsługiwana, więc -
'a'.__eq__(1)
jest wywołane, które również zwraca to samoNotImplemented
. - obiekty są traktowane tak jakby nie były takie same i zwracane jest
False
.
Oto mały, miły MCVE, który używa 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 true. To dlatego, że NotImplemented
jest rzeczywiście prawdziwą wartością:
bool(None.__eq__("a"))
# True
Tak samo jak,
bool(NotImplemented)
# True
Aby uzyskać więcej informacji na temat tego, jakie wartości są uważane za prawdziwe i fałszywe, zobacz sekcję docs na testowanie wartości prawdy , a także ta odpowiedź . Warto w tym miejscu zauważyć, że {[12] } jest prawdą, ale byłoby inaczej, gdyby Klasa zdefiniowała metodę __bool__
lub __len__
, która zwracała odpowiednio False
lub 0
.
Jeśli chcesz mieć funkcjonalny odpowiednik operatora ==
, użyj operator.eq
:
import operator
operator.eq(1, 'a')
# False
Jednak, jak wspomniano wcześniej, dla tego konkretnego scenariusza , w którym sprawdzasz None
, użyj is
:
var = 'a'
var is None
# False
var2 = None
var2 is None
# True
Funkcjonalny odpowiednik to jest za pomocą operator.is_
:
operator.is_(var2, None)
# True
None
jest obiektem specjalnym i tylko 1 wersja istnieje w pamięci w dowolnym momencie. IOW, jest jedynym singletonem klasy NoneType
(ale ten sam obiekt może mieć dowolną liczbę referencji). Wytyczne PEP8 wyrażają to wprost:
Porównania do singletonów jak
None
powinny być zawsze wykonywaneis
lubis not
, nigdy operatorów równości.
Podsumowując, dla singletonów takich jak None
, a sprawdzanie referencji z {[11] } jest bardziej odpowiednie, chociaż zarówno ==
, jak i is
będą działać dobrze.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2019-06-19 06:45:16
Wynik, który widzisz, jest spowodowany tym faktem, że
None.__eq__("a") # evaluates to NotImplemented
Ocenia na NotImplemented
, a wartość prawdy NotImplemented
jest udokumentowana jako True
:
Https://docs.python.org/3/library/constants.html
Wartość specjalna, która powinna być zwracana przez binarne metody specjalne (np.
__eq__()
,__lt__()
,__add__()
,__rsub__()
, itd.), aby wskazać, że operacja nie jest zaimplementowana w odniesieniu do innego typu; może być zwrócona przez specjalne metody binarne in-place (np.__imul__()
,__iand__()
, itd.) w tym samym celu. jego wartość prawdy jest prawdziwa.
Jeśli wywołujesz metodę __eq()__
ręcznie, a nie tylko za pomocą ==
, musisz być przygotowany, aby poradzić sobie z możliwością, że może ona zwrócić NotImplemented
i że jej wartość jest prawdziwa.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2019-04-22 11:56:55
Jak już się domyśliłeś None.__eq__("a")
ocenia na NotImplemented
jednak jeśli spróbujesz czegoś takiego
if NotImplemented:
print("Yes")
else:
print("No")
Wynik jest
Tak
Oznacza to, że wartość prawdyNotImplemented
true
W związku z tym wynik pytania jest oczywisty:
None.__eq__(something)
Wydajność NotImplemented
I bool(NotImplemented)
ocenia Na True
Więc if None.__eq__("a")
zawsze jest prawdą
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-12-31 14:54:21
Dlaczego?
Zwraca NotImplemented
, Tak:
>>> None.__eq__('a')
NotImplemented
>>>
Ale jeśli spojrzeć na to:
>>> bool(NotImplemented)
True
>>>
NotImplemented
jest to wartość prawdziwa, dlatego zwraca b
, wszystko, co jest True
przejdzie, wszystko, co jest False
nie przejdzie.
Jak to rozwiązać?
Musisz sprawdzić, czy jest True
, więc bądź bardziej podejrzliwy, jak widzisz:
>>> NotImplemented == True
False
>>>
Więc zrobisz:
>>> if None.__eq__('a') == True:
print('b')
>>>
I jak widzisz, to niczego nie zwróci.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2019-01-28 06:15:20