Чому "якщо None .__ eq __ (" a ")`, здається, оцінює True (але не зовсім)?


146

Якщо ви виконаєте наступне твердження в Python 3.7, воно (з мого тестування) надрукує b:

if None.__eq__("a"):
    print("b")

Однак, None.__eq__("a")оцінює до NotImplemented.

Природно, "a".__eq__("a")оцінює Trueі "b".__eq__("a")оцінює False.

Я спочатку виявив це під час тестування значення повернення функції, але у другому випадку нічого не повернув - значить, функція повернулася None.

Що тут відбувається?

Відповіді:


178

Це чудовий приклад того, чому __dunder__методи не повинні використовуватися безпосередньо, оскільки вони часто не є відповідними замінами для їх еквівалентних операторів; вам слід скористатися ==оператором замість порівняння рівності, або в цьому спеціальному випадку під час перевірки Noneвикористовувати is(пропустіть до нижньої частини відповіді для отримання додаткової інформації).

Ти зробив

None.__eq__('a')
# NotImplemented

Що повертається, NotImplementedоскільки типи, що порівнюються, різні. Розглянемо інший приклад, коли таким чином порівнюються два об'єкти різних типів, наприклад, 1та 'a'. Робити (1).__eq__('a')це також не правильно, і повернеться NotImplemented. Правильним способом порівняння цих двох значень було б рівність

1 == 'a'
# False

Що тут відбувається

  1. По-перше, (1).__eq__('a')пробується, який повертається NotImplemented. Це вказує на те, що операція не підтримується, тому
  2. 'a'.__eq__(1)називається, що також повертає те саме NotImplemented. Так,
  3. Об'єкти обробляються так, ніби вони не однакові, і Falseповертаються.

Ось приємний маленький MCVE за допомогою деяких спеціальних класів, щоб проілюструвати, як це відбувається:

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

Звичайно, це не пояснює, чому операція повертає істину. Це тому NotImplemented, що насправді є правдоподібною цінністю:

bool(None.__eq__("a"))
# True

Такий же, як,

bool(NotImplemented)
# True

Для отримання додаткової інформації про те, які значення вважаються неправдивими та хибними, дивіться розділ Документи на тестування цінності істини , а також цю відповідь . Тут варто зазначити, що NotImplementedце правда, але це була б інша історія, якби клас визначив a __bool__або __len__метод, який повернувся Falseабо 0відповідно.


Якщо ви хочете функціональний еквівалент ==оператора, використовуйте operator.eq:

import operator
operator.eq(1, 'a')
# False

Однак, як було сказано раніше, для цього конкретного сценарію , де ви перевіряєте None, використовуйте is:

var = 'a'
var is None
# False

var2 = None
var2 is None
# True

Функціональним еквівалентом цього є використання operator.is_:

operator.is_(var2, None)
# True

Noneє спеціальним об'єктом, і лише 1 версія існує в пам'яті в будь-який момент часу. IOW, це єдиний синглтон NoneTypeкласу (але той самий об'єкт може мати будь-яку кількість посилань). В рекомендації PEP8 зробити це явно:

Порівняння з одинаковими як Noneзавжди слід проводити з операторами рівності isабо is not, ніколи.

Підводячи підсумок, для одиночних кнопок доречніша Noneперевірка довідки is, хоч і те ==й інше isбуде працювати чудово.


33

Результат, який ви бачите, викликаний тим фактом, що

None.__eq__("a") # evaluates to NotImplemented

оцінює NotImplemented, а NotImplementedзначення правди задокументовано таким чином True:

https://docs.python.org/3/library/constants.html

Особливе значення , яке має бути повернено бінарними спеціальними методами (наприклад __eq__(), __lt__(), __add__(), __rsub__()і т.д.) , щоб вказати , що операція не здійснюється стосовно іншого типу; можуть бути повернуті з допомогою в місці бінарних спеціальних методів (наприклад __imul__(), __iand__()і т.д.) для тієї ж мети. Її цінність правдива.

Якщо ви називаєте __eq()__метод вручну, а не просто використовуєте ==, вам потрібно бути готовим розібратися з можливістю повернення NotImplementedйого і правдивим значенням.


16

Як ви вже зрозуміли, це None.__eq__("a")оцінює, NotImplementedякщо ви спробуєте щось подібне

if NotImplemented:
    print("Yes")
else:
    print("No")

результат є

так

це означає, що значення правди NotImplemented true

Тому результат питання очевидний:

None.__eq__(something) врожайність NotImplemented

І bool(NotImplemented)оцінює True

Так if None.__eq__("a")завжди правда


1

Чому?

Він повертає NotImplemented, так:

>>> None.__eq__('a')
NotImplemented
>>> 

Але якщо поглянути на це:

>>> bool(NotImplemented)
True
>>> 

NotImplementedнасправді є правдивою цінністю, тому воно повертається b, все, що є True, пройде, все, що не Falseбуде.

Як це вирішити?

Ви повинні перевірити, чи є True, тому будьте більш підозрілі, як бачите:

>>> NotImplemented == True
False
>>> 

Так ви зробите:

>>> if None.__eq__('a') == True:
    print('b')


>>> 

І як бачите, це нічого не поверне.


1
найбільш візуально чітка відповідь - v вартий доповнення - дякую
scharfmn

1
:) "Доречне доповнення" не зовсім приваблює те, що я намагався сказати (як ви бачите,) - можливо, "запізнілий досвід" - це те, чого я хотів - ура
scharfmn

@scharfmn так? Мені цікаво дізнатися, на вашу думку, ця відповідь додає, що раніше не висвітлювалося.
cs95

чомусь візуальні / відтворюючі речі тут додають ясності - повний демо
scharfmn

@scharfmn ... Яка прийнята відповідь також має, хоча підказки були видалені. Ви подавали заявку виключно через те, що підказки терміналу ліниво залишилися?
cs95
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.