为什么`if None .__ eq __(“ a”)`似乎评估为True(但不完全)?

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

这里发生了什么?

If you execute the following statement in Python 3.7, it will (from my testing) print b:

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

However, None.__eq__("a") evaluates to NotImplemented.

Naturally, "a".__eq__("a") evaluates to True, and "b".__eq__("a") evaluates to False.

I initially discovered this when testing the return value of a function, but didn’t return anything in the second case — so, the function returned None.

What’s going on here?


回答 0

这是一个很好的例子,说明为什么__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会工作得很好。

This is a great example of why the __dunder__ methods should not be used directly as they are quite often not appropriate replacements for their equivalent operators; you should use the == operator instead for equality comparisons, or in this special case, when checking for None, use is (skip to the bottom of the answer for more information).

You’ve done

None.__eq__('a')
# NotImplemented

Which returns NotImplemented since the types being compared are different. Consider another example where two objects with different types are being compared in this fashion, such as 1 and 'a'. Doing (1).__eq__('a') is also not correct, and will return NotImplemented. The right way to compare these two values for equality would be

1 == 'a'
# False

What happens here is

  1. First, (1).__eq__('a') is tried, which returns NotImplemented. This indicates that the operation is not supported, so
  2. 'a'.__eq__(1) is called, which also returns the same NotImplemented. So,
  3. The objects are treated as if they are not the same, and False is returned.

Here’s a nice little MCVE using some custom classes to illustrate how this happens:

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

Of course, that doesn’t explain why the operation returns true. This is because NotImplemented is actually a truthy value:

bool(None.__eq__("a"))
# True

Same as,

bool(NotImplemented)
# True

For more information on what values are considered truthy and falsy, see the docs section on Truth Value Testing, as well as this answer. It is worth noting here that NotImplemented is truthy, but it would have been a different story had the class defined a __bool__ or __len__ method that returned False or 0 respectively.


If you want the functional equivalent of the == operator, use operator.eq:

import operator
operator.eq(1, 'a')
# False

However, as mentioned earlier, for this specific scenario, where you are checking for None, use is:

var = 'a'
var is None
# False

var2 = None
var2 is None
# True

The functional equivalent of this is using operator.is_:

operator.is_(var2, None)
# True

None is a special object, and only 1 version exists in memory at any point of time. IOW, it is the sole singleton of the NoneType class (but the same object may have any number of references). The PEP8 guidelines make this explicit:

Comparisons to singletons like None should always be done with is or is not, never the equality operators.

In summary, for singletons like None, a reference check with is is more appropriate, although both == and is will work just fine.


回答 1

您看到的结果是由于以下事实造成的:

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 的可能性。

The result you are seeing is caused by that fact that

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

evaluates to NotImplemented, and NotImplemented‘s truth value is documented to be True:

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

Special value which should be returned by the binary special methods (e.g. __eq__(), __lt__(), __add__(), __rsub__(), etc.) to indicate that the operation is not implemented with respect to the other type; may be returned by the in-place binary special methods (e.g. __imul__(), __iand__(), etc.) for the same purpose. Its truth value is true.

If you call the __eq()__ method manually rather than just using ==, you need to be prepared to deal with the possibility it may return NotImplemented and that its truth value is true.


回答 2

正如您已经想过的None.__eq__("a")NotImplemented但是如果尝试类似

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

结果是

这意味着 NotImplemented true

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

None.__eq__(something) Yield NotImplemented

bool(NotImplemented)评估为True

所以if None.__eq__("a")永远是真的

As you already figured None.__eq__("a") evaluates to NotImplemented however if you try something like

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

the result is

yes

this mean that the truth value of NotImplemented true

Therefor the outcome of the question is obvious:

None.__eq__(something) yields NotImplemented

And bool(NotImplemented) evaluates to True

So if None.__eq__("a") is always True


回答 3

为什么?

它返回一个NotImplemented,是的:

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

但是,如果您看一下:

>>> bool(NotImplemented)
True
>>> 

NotImplemented实际上是一个真实的值,所以这就是它返回的原因b,任何True会通过的东西,不会通过的东西False

怎么解决呢?

您必须检查它是否为True,因此请更加可疑,如下所示:

>>> NotImplemented == True
False
>>> 

所以你会做:

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


>>> 

如您所见,它不会返回任何内容。

Why?

It returns a NotImplemented, yeah:

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

But if you look at this:

>>> bool(NotImplemented)
True
>>> 

NotImplemented is actually a truthy value, so that’s why it returns b, anything that is True will pass, anything that is False wouldn’t.

How to solve it?

You have to check if it is True, so be more suspicious, as you see:

>>> NotImplemented == True
False
>>> 

So you would do:

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


>>> 

And as you see, it wouldn’t return anything.