math.nan在使用'in'运算符时的悖论行为

9
我有以下代码行:

我有以下代码行:

import math as mt

...
...
...

        if mt.isnan (coord0):
            print (111111, coord0, type (coord0), coord0 in (None, mt.nan))
            print (222222, mt.nan, type (mt.nan), mt.nan in (None, mt.nan))

它会打印出:
111111 nan <class 'float'> False
222222 nan <class 'float'> True

我感到困惑... 有什么解释吗?

Python 3.6.0,Windows 10

我对Python解释器的质量非常有信心... 而且我知道,每当似乎计算机犯了一个错误时,实际上是我自己弄错了... 那我错在哪里呢?

[编辑]

(回应@COLDSPEED)

确实,ID是不同的:

print (111111, coord0, type (coord0), id (coord0), coord0 in (None, mt.nan))
print (222222, mt.nan, type (mt.nan), id (mt.nan), mt.nan in (None, mt.nan))

输出:

111111 nan <class 'float'> 2149940586968 False
222222 nan <class 'float'> 2151724423496 True

也许没有一个很好的理由,为什么nan不是真正的单例。但我还没有理解。在我看来,这种行为相当容易出错。
[编辑2]
(针对@Sven Marnach的反应)
仔细阅读@Sven Marnach的答案使我能够理解。这确实是一种妥协,当设计事物时会遇到这种情况。
冰还是很薄的:
如果a in(b,)返回True,如果id(a)== id(b),则似乎与IEEE-754标准不符,即nan应该不等于nan。
结论将是,虽然a在聚合中,但同时它也不在其中,因为聚合中的东西,即b必须按照IEEE标准被认为与a不相等。
我想从现在开始使用isnan...

我原以为是因为 math.nan != math.nan,但是 math.nan in [math.nan] 返回 True。猜测 coldspeed 可能是正确的。 - Jared Smith
我认为正在对ID进行检查。coord0中的nan具有与mt.nan常量不同的ID。 - cs95
2个回答

7
你看到的行为是Python中in运算符的一个优化产生的副作用,以及nan与自身的比较不相等,这是IEEE-754标准所要求的。
Python中的in运算符返回迭代器中是否有任何元素与你寻找的元素相等。表达式x in it本质上等同于any(x == y for y in it),但CPython还应用了额外的优化:为避免需要对每个元素调用__eq__,解释器首先检查xy是否是相同的对象,如果是,则立即返回True
这种优化对于几乎所有对象都是可行的。毕竟,相等性的基本属性之一是每个对象都与自身相等。但是,浮点数的IEEE-754标准要求nan != nan,因此NaN破坏了这个假设。这导致你看到的奇怪行为:如果一个nan恰好是迭代器中的一个相同的对象,则上述优化会导致in运算符返回True。但是,如果迭代器中的nan不是相同的对象,Python会退回到__eq__(),并返回False

1
说实话,NaN的实现完全是一团糟。当你不考虑后果地覆盖__eq__时,就会出现这种情况:
>>> n1 = math.nan
>>> n2 = math.nan
>>> id(n1) == id(n2)
True
>>> n1 == n2
False

只需使用isNan,让它们处理所有这些混乱。


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