示例问题:
import numpy as np
dc = dict()
dc[np.float('nan')] = 100
dc[np.float('nan')] = 200
正在为nan
创建多个条目,例如
dc.keys()
将生成{nan: 100, nan: 200}
,但应创建{nan: 200}
。
答案 0 :(得分:3)
您的问题(为什么将NaN
键添加到Python dict
创建多个条目的原因)的简短回答是因为浮点NaN
值无序,即NaN
值不等于,大于或小于任何值,包括其本身。此行为在浮点运算的IEEE 754标准中定义。为什么这样做是由IEEE 754委员会成员in this answer给出的。
对于更长时间,特定于Python的答案,让我们首先看看项目插入和密钥比较在CPython词典中是如何工作的。
当您说d[key] = val
时,会调用d
字典key
,而后者会调用(内部)PyDict_SetItem()
,这将更新现有字典项,或者插入一个新项目(可能会重新调整哈希表的大小)。
插入的第一步是在字典键的哈希表中查找lookdict
。在您的情况下(非字符串键)调用的通用查找函数是insertdict()
。
key
将使用key
的哈希值来查找key
,迭代具有相同哈希值的可能键列表,首先按地址进行比较,然后通过调用{{ 1}} s'等价运算符(有关Python lookdict()
实现中哈希冲突解决方案的更多详细信息,请参阅Objects/dictobject.c
中的优秀注释。)
由于每个float('nan')
具有相同的哈希值,但每个是一个不同的对象(具有不同的“身份”,即内存地址),并且他们不等于他们的浮动值:
>>> a, b = float('nan'), float('nan')
>>> hash(a), hash(b)
(0, 0)
>>> id(a), id(b)
(94753433907296, 94753433907272)
>>> a == b
False
当你说:
d = dict()
d[float('nan')] = 1
d[float('nan')] = 2
lookdict
将通过查看其哈希值(NaN
)搜索第二个0
,然后尝试通过使用相同哈希迭代密钥并通过按键比较来解决哈希冲突身份/地址(它们是不同的),然后通过调用(昂贵的)PyObject_RichCompareBool
/ open addressing,然后调用do_richcompare
来比较浮动,就像C一样:
/* Comparison is pretty much a nightmare. When comparing float to float,
* we do it as straightforwardly (and long-windedly) as conceivable, so
* that, e.g., Python x == y delivers the same result as the platform
* C x == y when x and/or y is a NaN.
其行为符合IEEE 754标准(来自float_richcompare
):
20.5.2 Infinity和NaN
[...]
基本操作和数学函数都接受无穷大和NaN并产生合理的输出。无穷大如人们所期望的那样通过计算传播:例如,2 +∞=∞,4 /∞= 0,atan(∞)=π/ 2。另一方面,NaN会感染任何涉及它的计算。除非计算产生相同的结果,无论实际值是否取代NaN,结果都是NaN。
在比较操作中,正无穷大大于除自身和NaN之外的所有值,负无穷大小于除自身和NaN之外的所有值。 NaN是无序的:它不等于,大于或小于任何东西,包括它自己。如果x的值是NaN,则x == x为false。您可以使用它来测试值是否为NaN,但是测试NaN的推荐方法是使用isnan函数(请参阅浮点类) )。此外,<,>,< =和> =会在应用于NaN时引发异常。
将为false
返回NaN == NaN
。
这就是为什么Python决定第二个NaN
对象值得一个新的字典条目。它可能具有相同的哈希值,但其地址和等价性测试表明它与所有其他NaN
对象不同。
但请注意,如果您始终使用相同的NaN
对象(具有相同的地址),因为地址在浮点等效之前进行了测试,您将获得预期的行为:< / p>
>>> nan = float('nan')
>>> d = dict()
>>> d[nan] = 1
>>> d[nan] = 2
>>> d
{nan: 2}
答案 1 :(得分:1)
由于历史原因解释here,
np.float('nan') == np.float('nan')
是假的。规则只是你不能有两个彼此相等的字典键 - 所以你可以有两个等于np.float('nan')
的键。
当然,这种行为违反直觉并且令人惊讶 - 因此您应该避免使用np.float('nan')
作为密钥。
答案 2 :(得分:0)
正如评论中提到的那样,nan
永远不会等同于#34;到另一个nan
,你的dict
正在为它写一个新密钥。这是大多数语言中nan
值的行为,而不仅仅是python。
我建议不要将它作为一个关键点,或至少解释它的目的,这样我们就能找到更好的方法来达到这个目的而不会陷入这样的陷阱。
在你的情况下,你可以自己测试这个行为:
a=list(dc.keys())
print(a[0]==a[1]) # will output False
上述代码(False
)的输出意味着对于系统而言,实际上是不会发生冲突的不同键