【问题标题】:NaNs as key in dictionariesNaN 作为字典中的键
【发布时间】:2011-09-20 11:24:39
【问题描述】:

谁能向我解释以下行为?

>>> import numpy as np
>>> {np.nan: 5}[np.nan]
5
>>> {float64(np.nan): 5}[float64(np.nan)]
KeyError: nan

为什么它在第一种情况下有效,而在第二种情况下无效? 此外,我发现以下确实有效:

>>> a ={a: 5}[a]
float64(np.nan)

【问题讨论】:

  • 这将永远是真的:float('nan') != float('nan')

标签: python numpy nan


【解决方案1】:

这里的问题是 NaN 不等于它自己,正如 IEEE 浮点数标准中定义的那样:

>>> float("nan") == float("nan")
False

当字典查找一个键时,它大致是这样的:

  1. 计算要查找的键的哈希值。

  2. 对于字典中具有相同哈希的每个键,检查它是否与要查找的键匹配。该检查包括

    一个。检查对象身份:如果字典中的键和要查找的键与is 运算符指示的对象相同,则找到键。

    b.如果第一次检查失败,请使用 __eq__ 运算符检查是否相等。

第一个例子成功了,因为np.nannp.nan 是同一个对象,所以它们不相等也没关系:

>>> numpy.nan is numpy.nan
True

在第二种情况下,np.float64(np.nan)np.float64(np.nan) 不是同一个对象——两个构造函数调用创建了两个不同的对象:

>>> numpy.float64(numpy.nan) is numpy.float64(numpy.nan)
False

由于对象也不比较相等,因此字典得出未找到键的结论并抛出KeyError

你甚至可以这样做:

>>> a = float("nan")
>>> b = float("nan")
>>> {a: 1, b: 2}
{nan: 1, nan: 2}

总之,避免 NaN 作为字典键似乎是一个更明智的想法。

【讨论】:

  • 最后一句话值得更多强调。
  • 是否保证所有float('nan') 具有相同的内存位置,即float('nan') 是单例?没有它,即使使用普通的float('nan') 也是一个坏主意。关于np.nan的同样问题。
  • @max:每次调用float('nan') 都会产生一个新的float 实例,就像每次调用float(1) 都会创建一个新的float 实例一样。这本身并不是一件坏事。 np.nan是NumPy模块中的全局名称,只要不重新赋值就会指向同一个对象,所以一般情况下np.nan是单个值。 (我不会称它为单例,因为这个名称是为只允许单个实例的类使用的,例如 NoneType。)
  • @SvenMarnach 嗯,这是有道理的。所以基本上,如果存储和查找都使用np.nan 完成,那么使用nandict 是安全的。 “狂野”nans,如 float('nan')float('inf') - float('inf') 创建的那些,不能用作字典键。
  • 刚刚意识到这是从related question 发生的,并且玩弄它,我意识到以下解决方法会删除密钥:假设您的字典是d,然后删除然后@ 987654351@键:for k in d.keys():if k!=k: del d[k]; break。知道为什么这会删除密钥但直接d[float('nan')] 失败吗?具有相同id 的 NaN 也不等于它们自己。
【解决方案2】:

请注意,Python 3.6 不再是这种情况:

>>> d = float("nan") #object nan
>>> d
nan
>>> c = {"a": 3, d: 4}
>>> c["a"]
3
>>> c[d]
4

在此示例中,c 是一个字典,其中包含与键“a”关联的值 3 和与键 NaN 关联的值 4。

Python 3.6 内部在字典中查找的方式发生了变化。现在,它做的第一件事是比较表示基础变量的两个指针。如果它们指向同一个对象,那么这两个对象被认为是相同的(嗯,从技术上讲,我们是在将一个对象与它自身进行比较)。否则,比较它们的散列,如果散列不同,则认为这两个对象不同。如果此时尚未确定对象的相等性,则调用它们的比较器(可以说,它们是“手动”比较的)。

这意味着尽管 IEEE754 指定 NAN 不等于它自己:

>>> d == d
False

查找字典时,首先要比较的是变量的底层指针。因为它们指向同一个对象 NaN,所以字典返回 4。

另请注意,并非所有 NaN 对象都完全相同:

>>> e = float("nan")
>>> e == d
False
>>> c[e]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
KeyError: nan
>>> c[d]
4

所以,总结一下。字典通过尝试比较底层对象是否相同来优先考虑性能。他们有哈希比较和比较作为后备。此外,并非每个 NaN 都代表相同的底层对象。

在处理作为字典键的 NaN 时必须非常小心,添加这样的键会使基础值无法达到,除非您依赖此处描述的属性。此属性可能会在未来发生变化(不太可能,但可能)。小心行事。

【讨论】:

  • 这似乎在 python 3.8 上再次失败
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2017-12-15
  • 2022-08-22
  • 2020-03-11
  • 1970-01-01
  • 2021-08-24
  • 1970-01-01
相关资源
最近更新 更多