检查容器中是否存在NaN
当我检查列表或集合中的NaN时,NaN会得到完美处理。但我不知道如何。[更新:不,不是;如果找到相同的NaN实例,则报告为存在;如果仅找到不相同的NaN实例,则将其报告为不存在。]
-
我认为列表中的存在是通过相等性测试的,所以我希望因为NaN!= NaN而找不到NaN。
-
hash(NaN)和hash(0)均为0。字典和集合如何区分NaN和0?
-
使用
in
运算符检查任意容器中是否存在NaN是否安全?还是依赖于实现?
我的问题是关于Python 3.2.1;但是如果将来的版本中已有/计划进行任何更改,我也想知道。
NaN = float('nan')
print(NaN != NaN) # True
print(NaN == NaN) # False
list_ = (1, 2, NaN)
print(NaN in list_) # True; works fine but how?
set_ = {1, 2, NaN}
print(NaN in set_) # True; hash(NaN) is some fixed integer, so no surprise here
print(hash(0)) # 0
print(hash(NaN)) # 0
set_ = {1, 2, 0}
print(NaN in set_) # False; works fine, but how?
请注意,如果我将用户定义的类的实例添加到中list
,然后检查是否包含,则__eq__
至少在CPython中调用该实例的方法(如果已定义)。这就是为什么我假定list
使用operator来测试遏制的原因==
。
编辑:
每罗马的答案,这似乎是__contains__
对list
,tuple
,set
,dict
在一个非常奇怪的方式行事:
def __contains__(self, x):
for element in self:
if x is element:
return True
if x == element:
return True
return False
我说“奇怪”是因为我没有在文档中看到它的解释(也许我错过了),而且我认为这不应该作为实现选择。
当然,一个NaN对象可能与id
另一个NaN对象不同(在意义上)。(这并不令人感到意外;
Python不能保证这种身份。事实上,我从未见过CPython共享一个在不同地方创建的NaN实例,即使它共享一个少量或短字符串的实例。)这意味着未定义在内置容器中是否存在NaN的测试。
这是非常危险且非常微妙的。有人可能会运行我上面显示的代码,并错误地得出结论,使用可以安全地测试NaN成员身份in
。
我认为没有解决此问题的完美方法。一种非常安全的方法是确保绝对不要将NaN添加到内置容器中。(检查整个代码是很痛苦的……)
另一种选择是in
当心左侧可能存在NaN的情况,并在这种情况下使用单独测试NaN成员身份math.isnan()
。另外,还需要避免或重写其他操作(例如,设置交集)。
-
问题#1:为什么在同一对象中的容器中会发现NaN。
从文档中:
对于列表,元组,集合,frozenset,dict或collections.deque等容器类型,y中的表达式x等于any(x为e或y中的e为x ==
e)。这正是我在NaN上观察到的,所以一切都很好。为什么要这个规则?我怀疑这是因为
dict
/set
想要诚实地报告它包含某个对象(如果该对象确实存在于其中)(即使__eq__()
出于某种原因选择报告该对象不等于自身)。问题2:为什么NaN的哈希值与0相同?
从文档中:
由内置函数hash()调用,用于对散列集合的成员(包括set,frozenset和dict)进行操作。 hash
()应该返回一个整数。唯一需要的属性是比较相等的对象具有相同的哈希值;建议以某种方式将散列值混合在一起(例如,使用异或),以将对象的组成部分也用作对象比较的一部分。请注意,此要求仅在一个方向上进行;具有相同散列的对象不必相等!起初我以为是错字,但后来我意识到不是。哈希冲突无论如何都会发生,即使默认情况下也是如此
__hash__()
(请参阅此处的出色说明)。容器可以毫无问题地处理碰撞。当然,它们确实最终会使用==
运算符来比较元素,因此,只要它们不相同,它们很容易以多个NaN值结束!尝试这个:>>> nan1 = float('nan') >>> nan2 = float('nan') >>> d = {} >>> d[nan1] = 1 >>> d[nan2] = 2 >>> d[nan1] 1 >>> d[nan2] 2
因此,一切工作均按文档记录。但是…非常非常危险!有多少人知道NaN的多种价值可以在一个命令中彼此并存?有多少人会觉得这很容易调试?
我建议将NaN设为
float
不支持哈希的子类的实例,因此不能意外地将其添加到set
/中dict
。我将其提交给python-ideas。最后,我在这里的文档中发现了一个错误:
对于不定义用户定义的类
__contains__()
,但不限定__iter__()
,x in y
如果一些值为truez
与x == z
同时迭代产生y
。如果在迭代过程中in
引发了异常,则好像引发了该异常。最后,尝试使用旧式的迭代协议:如果一个类定义
__getitem__()
,x in y
则当且仅当存在一个非负整数索引(i
例如)x == y[i]
,并且所有较低的整数索引都不会引发IndexError
异常时,该方法才为true
。(如果引发了其他任何异常,则好像in
引发了该异常)。您可能会注意到,
is
与内置容器不同,这里没有提及。我对此感到惊讶,因此我尝试:>>> nan1 = float('nan') >>> nan2 = float('nan') >>> class Cont: ... def __iter__(self): ... yield nan1 ... >>> c = Cont() >>> nan1 in c True >>> nan2 in c False
如您所见,先检查身份,然后再检查
==
-与内置容器一致。我将提交报告以修复文档。