Почему "if None.__ eq __ (" a ")" кажется равным True (но не совсем)?

Если вы выполните следующую инструкцию в Python 3.7, она (из моего тестирования) выведет b:

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

Однако None.__eq__("a") оценивается как NotImplemented.

Естественно, "a".__eq__("a") оценивается как True, а "b".__eq__("a") оценивается как False.

Сначала я обнаружил это при тестировании возвращаемого значения функции, но во втором случае ничего не возвращал, поэтому функция вернула None.

Что здесь происходит?

Ответ 1

Это отличный пример того, почему методы __dunder__ не должны использоваться напрямую, поскольку они часто не являются подходящими заменами для их эквивалентных операторов; Вместо этого вы должны использовать оператор == для сравнений на равенство, или в этом особом случае, когда проверяете None, используйте is (пропустите до конца ответа для получения дополнительной информации).

Ты сделал

None.__eq__('a')
# NotImplemented

Который возвращает NotImplemented поскольку сравниваемые типы различны. Рассмотрим другой пример, в котором два объекта с разными типами сравниваются таким образом, например, 1 и 'a'. Выполнение (1).__eq__('a') также неверно и вернет NotImplemented. Правильный способ сравнить эти два значения на равенство

1 == 'a'
# False

Что здесь происходит

  1. Сначала NotImplemented (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 является правдивым, но было бы иначе, если бы класс определил метод __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 версия. Итак, это единственный синглтон класса NoneType (но один и тот же объект может иметь любое количество ссылок). Рекомендации PEP8 делают это явным:

Сравнения с синглетами, такими как None всегда должны выполняться с оператором is или is not, никогда с операторами равенства.

Таким образом, для одиночек, как None, ссылка чек с is является более подходящим, хотя и == и is будет прекрасно работать.

Ответ 2

Результат, который вы видите, вызван тем фактом, что

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

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

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

Специальное значение, которое должно возвращаться двоичными специальными методами (например, __eq__(), __lt__(), __add__(), __rsub__() и т.д.), Чтобы указать, что операция не реализована в отношении другого типа; могут быть возвращены двоичными специальными методами на месте (например, __imul__(), __iand__() и т.д.) для той же цели. Его истинное значение верно.

Если вы вызываете метод __eq()__ вручную, а не просто используете ==, вы должны быть готовы к тому, что он может вернуть NotImplemented и что его истинное значение равно true.

Ответ 3

Как вы уже поняли, None.__eq__("a") оценивается как NotImplemented однако, если вы попробуете что-то вроде

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

результат

да

это означает, что истинное значение NotImplemented true

Поэтому исход вопроса очевиден:

None.__eq__(something) дает NotImplemented

И bool(NotImplemented) оценивается как True

Так что if None.__eq__("a") всегда True

Ответ 4

Зачем?

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

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

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

>>> bool(NotImplemented)
True
>>> 

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

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

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

>>> NotImplemented == True
False
>>> 

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

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


>>> 

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

Ответ 5

Потому что это не оценивает Ложь; по умолчанию все объекты считаются истинными, если они не имеют длины 0 (контейнеры) или не равны нулю (числовые); см. справочник по проверке истинности ценности.

Однако, возвращая NotImplemented сигналы в Python, что тест на равенство не реализован, и наоборот (1). вместо этого используется eq (a). Если этот метод также не существует, объекты не равны, если они не являются одним и тем же объектом (a равно 1 - False).

Другими словами, NotImplemented - это специальный одноэлементный объект, сторож, сигнализирующий Python, что вы хотите, чтобы Python попробовал что-то еще, так как проверка на равенство между этим объектом и другим не поддерживается.

Как таковое, оно никогда не предназначалось для использования в логическом контексте. Это никогда не предназначено, чтобы передать Ложь.

DigitalOcean SiteGround iPage