为啥 `if None.__eq__("a")` 似乎评估为 True(但不完全)?
Posted
技术标签:
【中文标题】为啥 `if None.__eq__("a")` 似乎评估为 True(但不完全)?【英文标题】:Why does `if None.__eq__("a")` seem to evaluate to True (but not quite)?为什么 `if None.__eq__("a")` 似乎评估为 True(但不完全)? 【发布时间】:2019-05-27 18:56:39 【问题描述】:如果您在 Python 3.7 中执行以下语句,它将(根据我的测试)打印 b
:
if None.__eq__("a"):
print("b")
但是,None.__eq__("a")
的计算结果为 NotImplemented
。
当然,"a".__eq__("a")
的计算结果为 True
,"b".__eq__("a")
的计算结果为 False
。
我最初在测试函数的返回值时发现了这一点,但在第二种情况下没有返回任何内容——因此,函数返回了None
。
这是怎么回事?
【问题讨论】:
【参考方案1】:这是一个很好的例子,说明为什么不应直接使用 __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
有关哪些值被认为是真值和假值的更多信息,请参阅Truth Value Testing 和this answer 上的文档部分。这里值得注意的是,NotImplemented
是真实的,但如果该类定义了一个分别返回 False
或 0
的 __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
是一个特殊的对象,任何时候内存中只存在一个版本。 IOW,它是 NoneType
类的唯一单例(但同一个对象可能有任意数量的引用)。 PEP8 guidelines 明确表示:
与
None
等单例的比较应始终使用is
或is not
,绝不是相等运算符。
总之,对于像None
这样的单例,使用is
进行参考检查更合适,尽管==
和is
都可以正常工作。
【讨论】:
【参考方案2】:您看到的结果是由以下事实引起的
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
并且它的真值为真的可能性。
【讨论】:
【参考方案3】:正如您已经想到的那样,None.__eq__("a")
的计算结果为 NotImplemented
,但是如果您尝试类似的方法
if NotImplemented:
print("Yes")
else:
print("No")
结果是
是的
这表示NotImplemented
true
的真值
因此问题的结果是显而易见的:
None.__eq__(something)
产生NotImplemented
而bool(NotImplemented)
的计算结果为真
所以if None.__eq__("a")
始终为真
【讨论】:
【参考方案4】:为什么?
它返回一个NotImplemented
,是的:
>>> None.__eq__('a')
NotImplemented
>>>
但是如果你看这个:
>>> bool(NotImplemented)
True
>>>
NotImplemented
实际上是一个真值,所以它返回 b
,任何 True
都会通过,任何 False
都不会。
如何解决?
你必须检查它是否是True
,所以更加可疑,如你所见:
>>> NotImplemented == True
False
>>>
所以你会这样做:
>>> if None.__eq__('a') == True:
print('b')
>>>
如您所见,它不会返回任何内容。
【讨论】:
最直观的答案 - v 值得补充 - 谢谢 :) “值得添加”并没有完全捕捉到我想说的(正如你所看到的)——也许“迟来的卓越”才是我想要的——干杯 @scharfmn 是吗?我很想知道您认为这个答案增加了什么以前没有涉及的内容。 这里的视觉/复制内容以某种方式增加了清晰度 - 完整演示 @scharfmn ... 尽管提示已被删除,但已接受的答案也有。您是否仅仅因为终端提示被懒惰地留在里面而投票?以上是关于为啥 `if None.__eq__("a")` 似乎评估为 True(但不完全)?的主要内容,如果未能解决你的问题,请参考以下文章
为啥为 __eq__ 定义参数类型会引发 MyPy 类型错误?
为啥 Python 有一个 __ne__ 运算符方法而不仅仅是 __eq__?
为啥 Python 没有“__req__”(反射相等)方法?
为啥 def main(argv=[__name__]) 和 if __name__ == "__main__": sys.exit(main(sys.argv))?