Ответ 1
Это отличный пример того, почему методы __dunder__
не должны использоваться напрямую, поскольку они часто не являются подходящими заменами для их эквивалентных операторов; Вместо этого вы должны использовать оператор ==
для сравнений на равенство, или в этом особом случае, когда проверяете None
, используйте is
(пропустите до конца ответа для получения дополнительной информации).
Ты сделал
None.__eq__('a')
# NotImplemented
Который возвращает NotImplemented
поскольку сравниваемые типы различны. Рассмотрим другой пример, в котором два объекта с разными типами сравниваются таким образом, например, 1
и 'a'
. Выполнение (1).__eq__('a')
также неверно и вернет NotImplemented
. Правильный способ сравнить эти два значения на равенство
1 == 'a'
# False
Что здесь происходит
- Сначала
NotImplemented
(1).__eq__('a')
, которая возвращаетNotImplemented
. Это указывает на то, что операция не поддерживается, поэтому -
'a'.__eq__(1)
вызывается, что также возвращает тот жеNotImplemented
. Так, - Объекты обрабатываются так, как будто они не совпадают, и возвращается значение
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
будет прекрасно работать.