为什么`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

这里发生了什么?

Answers:


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是truthy,但它会是一个不同的故事有类中定义一个__bool____len__方法返回False0分别。


如果要==使用与运算符等效的功能,请使用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应始终使用isis 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并且其真实值是true 的可能性。


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 '19

不知何故,此处的视觉/替换事物使清晰度更加清晰-完整演示
scharfmn

@scharfmn ...尽管已删除了提示,但也可以接受该答案。您是否仅因为终端提示被滞后地投票支持?
cs95
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.