为什么 `if None.__eq__("a")` 看起来会评估为True(但实际上不是)?

148

如果您在Python 3.7中执行以下语句,则会(根据我的测试)打印b

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

然而,None.__eq__("a")的结果是NotImplemented

显然,"a".__eq__("a")的结果是True"b".__eq__("a")的结果是False

我最初在测试函数的返回值时发现这一点,在第二种情况下没有返回任何东西——因此,该函数返回了None

这里到底发生了什么?

4个回答

182
这是一个很好的例子,说明为什么不应该直接使用__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

要了解哪些值被视为真和假,请参见文档中关于Truth Value Testing的部分,以及此回答。需要注意的是,NotImplemented是真值,但如果该类定义了返回False0__bool____len__方法,情况就会不同。


如果你想使用功能等同于==运算符的,使用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是一个特殊对象,在任何时候只有一个版本存在于内存中。也就是说,它是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__()等)返回相同的目的。它的真值为true。

如果您手动调用__eq()__方法而不仅仅使用==,则需要准备好处理它可能会返回NotImplemented并且其真值为true的可能性。


16

正如你已经发现的那样,None.__eq__("a") 的求值结果为 NotImplemented,但如果你尝试类似下面的东西

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

结果是

是的

这意味着 NotImplemented 的真值为 true

因此问题的结果是显而易见的:

None.__eq__(something) 返回 NotImplemented

bool(NotImplemented) 的值为 True

所以如果 None.__eq__("a") 始终为True


1

为什么?

它会返回一个 NotImplemented,是的:

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

但是如果你看这个:

但是如果你看这个:

>>> bool(NotImplemented)
True
>>> 

NotImplemented实际上是一个真值,这就是为什么它返回b,任何True的东西都会通过,任何False的东西都不会通过。

如何解决?

你必须检查它是否为True,所以要更加怀疑,就像你看到的:

>>> NotImplemented == True
False
>>> 

So you would do:

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


>>> 

而且正如你所看到的,它不会返回任何东西。

1
最直观清晰的答案 - 非常值得添加 - 谢谢 - scharfmn
1
“值得的补充”并不能完全表达我想说的意思(正如你所看到的)- 也许“迟来的卓越”是我想要的 - 干杯 - scharfmn
@scharfmn 是吗?我很好奇您认为这个答案增加的内容是什么,之前已经没有提到过的。 - cs95
不知何故,这里的可视化/REPL工具增加了清晰度 - 完整演示 - scharfmn
@scharfmn... 接受的答案中也有这一点,尽管提示已被删除。您仅仅因为终端提示懒散地留下了它们,就给出了赞同吗? - cs95
我明白你的意思。可能吗?同意它是寄生的。但它帮助了我。这就是为什么我评论,以解释。这是对你的答案的致敬,我现在已经点赞了。 - scharfmn

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接