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