Почему `if 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.

Что тут происходит?

AI Архитектор
источник

Ответы:

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

Конечно, это не объясняет, почему операция возвращает 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будут работать просто отлично.

cs95
источник
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")всегда верно

Kanjiu
источник
1

Зачем?

Это возвращает NotImplemented, да:

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

Но если вы посмотрите на это:

>>> bool(NotImplemented)
True
>>> 

NotImplementedна самом деле истинное значение, поэтому оно и возвращает b, все, что Trueбудет, пройдет, все, что не Falseбудет.

Как это решить?

Вы должны проверить, если это Trueтак, поэтому будьте более подозрительны, как вы видите:

>>> NotImplemented == True
False
>>> 

Так вы бы сделали:

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


>>> 

И, как вы видите, это ничего не вернет.

U10-Forward
источник
1
наиболее наглядный ответ - v стоящее дополнение - спасибо
scharfmn
1
:) «стоящее дополнение» не совсем отражает то, что я пытался сказать (как вы видите) - возможно, «запоздалое превосходство» - это то, что я хотел - ура
scharfmn
@scharfmn да? Мне любопытно узнать, что вы думаете, этот ответ добавляет, который еще не был покрыт раньше.
cs95
каким-то образом визуальные / реплинговые вещи здесь добавляют ясности - полная демонстрация
scharfmn
@scharfmn ... Какой принятый ответ также имел, хотя подсказки были удалены. Вы поддержали голосование исключительно потому, что подсказки терминала были лениво оставлены?
cs95