检查容器中是不是存在 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 listtuplesetdict 的行为似乎非常奇怪:

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__()但定义的用户定义的类 如果 zx == 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'),1False;所以看起来set 使用id 作为散列函数而不是hash。同样,这很奇怪,因为存在 float('nan').__hash__(并计算为 0)。不用说,我 100% 同意您关于 in 对 NaN 不安全的回答! :) 我理解(并期望)is 不会调用 __eq__。我担心的是使用is 代替__eq__ 来测试列表中是否存在对象。 is 可能在相同的字符串或数字上评估为 False,因此它不是成员资格测试的正确方法。 @max 但如果计算结果为 True,则对象的值不应被视为不同。 例如在我的机器上:a = 1.2345678b = 1.2345678; a is b 计算结果为 False,但 a in (b,) 计算结果为 True(更好!)。因此,很明显列表成员是使用__eq__ 测试的,而不是is。如果 Python 对某些浮点数使用 is,而对其他浮点数使用 __eq__,这似乎是一个错误。 无论如何,+1 用于发现我认为不可能发生的奇怪行为!

以上是关于检查容器中是不是存在 NaN的主要内容,如果未能解决你的问题,请参考以下文章

Flutter检查json显示/隐藏容器中是不是存在值

R - 检查 r 数据框行的任何列中是不是存在 NA,如果存在,则删除该行 [重复]

Python:排序函数在存在 nan 时中断

检查 Azure 存储中是不是存在 blob

如何检查MySQL中是不是存在一行? (即检查电子邮件是不是存在于 MySQL 中)

如何检查MySQL中是不是存在行? (即检查电子邮件是不是存在于 MySQL 中)