Python 3.6字典中含NaN元组的可哈希对象异常行为问询
关于Python字典+NaN的“意外行为”解释
首先咱们把场景还原清楚——你创建了一个生成含三个NaN的元组的lambda:
null_generator = lambda: (float('nan'), float('nan'), float('nan'))
然后发现两次调用这个lambda得到的不同元组对象,在结合Pandas使用时表现出了不符合直觉的行为,对吧?
核心原因:NaN的特殊特性
这其实不是字典的bug,而是float('nan')本身的IEEE 754标准特性导致的,结合Python和Pandas的不同处理逻辑,就出现了“意外”:
- NaN的相等性:根据标准,
NaN != NaN——你可以直接在终端测试:>>> float('nan') == float('nan') False - NaN的哈希值:但在CPython中,
hash(float('nan'))会返回一个固定值(比如3.6版本里是0),也就是说所有NaN的哈希值都是相同的:>>> hash(float('nan')) == hash(float('nan')) True
原生Python字典的表现
对于原生Python字典来说,判断两个键是否“相同”需要满足两个条件:哈希值相等,且两个对象用==比较返回True。
所以如果你把两次生成的含NaN的元组作为键:
a = null_generator() b = null_generator() d = {} d[a] = "first" d[b] = "second" print(d)
你会得到一个包含两个键的字典,因为a == b返回False(逐元素比较都是NaN,而NaN不等于自己),哪怕它们的哈希值相同。
Pandas中的特殊处理
但你是在使用Pandas时发现的问题,这就涉及到Pandas对NaN的特殊逻辑了:
Pandas为了方便数据处理,刻意将NaN视为相等的索引/键。比如当你用含NaN的元组作为pd.Index或者pd.Series的索引时,Pandas会把它们当成同一个索引项,这和原生Python字典的行为不一样。
举个例子:
import pandas as pd s = pd.Series([1, 2], index=[null_generator(), null_generator()]) print(s)
你会发现最终的Series只有一个索引项,第二个值覆盖了第一个,因为Pandas把这两个含NaN的元组视为相同的索引。
怎么避免这个问题?
如果你需要在Pandas中区分这些含NaN的元组,可以考虑:
- 用其他标识代替NaN,比如自定义一个特殊对象(比如
class NullMarker: pass,然后用NullMarker()代替float('nan')),这样每个对象都是唯一的,哈希值和相等性都符合直觉。 - 如果必须用NaN,可以额外添加一个唯一标识字段,把元组扩展成
(nan, nan, nan, unique_id),这样即使前三个元素是NaN,第四个唯一ID也能让它们成为不同的键/索引。
内容的提问来源于stack exchange,提问作者Nikhil Shinday
相关产品推荐
相关产品推荐

