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.

Co tu się dzieje?
Author: kmario23, 2018-12-31

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

  1. pierwszy, (1).__eq__('a') jest wypróbowany, który zwraca NotImplemented. Oznacza to, że operacja nie jest obsługiwana, więc
  2. 'a'.__eq__(1) jest wywołane, które również zwraca to samo NotImplemented.
  3. 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 wykonywane is lub is 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.

 181
Author: cs95,
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.

 33
Author: Mark M,
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ą

 16
Author: Kami Kaze,
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.

 1
Author: U11-Forward,
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