检查容器中是不是存在 NaN
Posted
技术标签:
【中文标题】检查容器中是不是存在 NaN【英文标题】:Checking for NaN presence in a container检查容器中是否存在 NaN 【发布时间】:2012-04-11 21:06:13 【问题描述】:当我检查它在列表或集合中的存在时,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
包含使用运算符 ==
进行测试。
编辑:
根据 Roman 的回答,__contains__
for 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 对象可能与另一个 NaN 对象不同(在id
的意义上)。 (这并不奇怪;Python 不保证这样的身份。事实上,我从未见过 CPython 共享在不同位置创建的 NaN 实例,即使它共享一个小数字或短字符串的实例。)这意味着在内置容器中测试 NaN 的存在是未定义的。
这是非常危险的,也是非常微妙的。有人可能会运行我上面显示的代码,并错误地得出使用 in
测试 NaN 成员资格是安全的结论。
我认为这个问题没有完美的解决方法。一种非常安全的方法是确保永远不会将 NaN 添加到内置容器中。 (检查整个代码很痛苦......)
另一种选择是注意in
左侧可能有NaN 的情况,在这种情况下,使用math.isnan()
单独测试NaN 成员资格。此外,其他操作(例如,设置交集)也需要避免或重写。
【问题讨论】:
底线:为了安全起见使用:any(math.isnan(element) for element in list_) @jsbueno: 是的......但这对设置交集问题没有帮助;它也不处理for x in cont1: if x in cont2 do something
的情况...我想说的底线是“非常害怕,只是希望你不要忽视一些事情”
这无济于事-您必须同意没有简单的解决方案。您可以使用上述循环将任何 NaN 转换为读取“NaN”的字符串 - 这些将明确比较。
【参考方案1】:
问题 #1:为什么在容器中发现 NaN 是相同的对象。
来自documentation:
对于容器类型,例如 list、tuple、set、frozenset、dict 或 collections.deque,表达式 x in y 等价于 any(x is e 或 x == e for e in y)。
这正是我用 NaN 观察到的,所以一切都很好。为什么有这个规则?我怀疑这是因为 dict
/set
想要诚实地报告它包含某个对象,如果该对象实际上在其中(即使 __eq__()
出于任何原因选择报告该对象不等于它自己) .
问题 #2:为什么 NaN 的哈希值与 0 相同?
来自documentation:
由内置函数 hash() 调用,用于对 散列集合,包括 set、frozenset 和 dict。 散列() 应该返回一个整数。唯一需要的属性是对象 比较相等的具有相同的哈希值;建议以某种方式 将哈希值混合在一起(例如,使用异或) 对象的组成部分也起到了比较的作用 对象。
注意,要求只有一个方向;具有相同哈希的对象不必相等!一开始我以为是笔误,后来才发现不是。无论如何,哈希冲突都会发生,即使是默认的__hash__()
(参见here 的精彩解释)。容器处理碰撞没有任何问题。当然,它们最终会使用 ==
运算符来比较元素,因此它们很容易得到多个 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。
最后发现文档有错误here:
对于没有定义
__contains__()
但定义的用户定义的类 如果z
和x == z
的某个值是,则定义__iter__()
,x in y
为真 在迭代y
时产生。如果在执行期间引发异常 迭代,就好像in
引发了那个异常。最后,尝试旧式迭代协议:如果一个类定义
__getitem__()
,x in y
为真当且仅当存在非负 整数索引i
使得x == y[i]
和所有较低的整数索引 不引发IndexError
异常。 (如果引发任何其他异常,它 好像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
如您所见,首先检查身份,然后再检查==
- 与内置容器一致。我将提交报告以修复文档。
【讨论】:
您可能对此 bugs.python.org 问题感兴趣:bugs.python.org/issue11945【参考方案2】:我无法使用 float('nan')
而不是 NaN
来重现您的元组/设置案例。
所以我认为它之所以起作用只是因为id(NaN) == id(NaN)
,即NaN
对象没有实习:
>>> NaN = float('NaN')
>>> id(NaN)
34373956456
>>> id(float('NaN'))
34373956480
和
>>> NaN is NaN
True
>>> NaN is float('NaN')
False
我相信 tuple/set 查找对相同对象的比较有一些优化。
回答您的问题 - 在检查 NaN
是否存在时,在 in
运算符上进行中继是不安全的。如果可能,我建议使用None
。
只是一个评论。 __eq__
与 is
语句无关,在查找过程中,对象 id 的比较似乎发生在任何值比较之前:
>>> class A(object):
... def __eq__(*args):
... print '__eq__'
...
>>> A() == A()
__eq__ # as expected
>>> A() is A()
False # `is` checks only ids
>>> A() in [A()]
__eq__ # as expected
False
>>> a = A()
>>> a in [a]
True # surprise!
【讨论】:
哇,这很奇怪。float('nan')
的类型是 float
,而 float
定义了 __eq__
,所以我不明白 Python 会如何回退到使用 id
来检查是否相等。此外,当我按照您的示例进行操作时,我发现float('nan') in float('nan'),1
是False
;所以看起来set
使用id
作为散列函数而不是hash
。同样,这很奇怪,因为存在 float('nan').__hash__
(并计算为 0
)。不用说,我 100% 同意您关于 in
对 NaN 不安全的回答! :)
我理解(并期望)is
不会调用 __eq__
。我担心的是使用is
代替__eq__
来测试列表中是否存在对象。 is
可能在相同的字符串或数字上评估为 False,因此它不是成员资格测试的正确方法。
@max 但如果计算结果为 True
,则对象的值不应被视为不同。
例如在我的机器上:a = 1.2345678
; b = 1.2345678
; a is b
计算结果为 False
,但 a in (b,)
计算结果为 True
(更好!)。因此,很明显列表成员是使用__eq__
测试的,而不是is
。如果 Python 对某些浮点数使用 is
,而对其他浮点数使用 __eq__
,这似乎是一个错误。
无论如何,+1 用于发现我认为不可能发生的奇怪行为!以上是关于检查容器中是不是存在 NaN的主要内容,如果未能解决你的问题,请参考以下文章
R - 检查 r 数据框行的任何列中是不是存在 NA,如果存在,则删除该行 [重复]