Python中直接比较字典与转集合比较的差异及异常排查
一、直接用==比较字典 vs 转集合后比较的核心差异
这两种方式的逻辑完全不一样,我给你拆解清楚:
字典
==比较的是「完整的键值对匹配」
当你用dict1 == dict2时,Python会做这几件事:- 检查两个字典的键是否完全一致(数量、内容都得一模一样)
- 对每个键,检查对应的值是否相等(包括嵌套结构,比如字典里的字典、列表都会递归比较)
- 划重点:从Python 3.7开始,字典是插入有序的,但
==比较完全不关心键的顺序——只要所有键值对都匹配,哪怕顺序天差地别,结果都是True
转集合比较的是「键值对的存在性」
当你把字典转成集合(比如set(dict.items())),你其实是把每个键值对转成了(key, value)元组,然后用集合来比较。集合的特性是无序、去重,所以它只关心两个集合里的元素(也就是键值对元组)是否完全相同,不关心顺序。但要注意:如果你的字典里有不可哈希的值(比如列表、自定义字典这类无法被哈希的类型),这种方式根本用不了,会直接抛出TypeError。
二、你的单元测试问题:字典==False,转集合后True
结合你的场景(网站接口数据 vs Postman保存的本地数据),我猜大概率是下面两种情况之一:
情况1:字典里包含NaN值
这是最常见的原因!因为在Python中,float('nan')(非数字)有个特殊性质:它不等于任何值,包括它自己。比如:
>>> float('nan') == float('nan') False
所以如果你的两个字典里,某个键对应的值都是NaN,那么字典==比较时,这个键值对会被判定为不相等,导致整个比较返回False。
但当你把键值对转成集合时,Python对NaN的哈希值做了特殊处理:所有NaN的哈希值都是相同的。所以集合会认为(key, nan)这个元组是同一个元素,因此集合比较会返回True。
举个例子验证:
dict_from_api = {"id": 123, "score": float('nan')} dict_from_postman = {"score": float('nan'), "id": 123} print(dict_from_api == dict_from_postman) # False print(set(dict_from_api.items()) == set(dict_from_postman.items())) # True
情况2:你使用了自定义字典类,重写了__eq__方法
如果网站返回的字典不是Python原生的dict,而是某个库自定义的字典子类,并且这个子类错误地重写了__eq__方法,把键的顺序也纳入了比较逻辑(比如直接比较list(dict.items())),那么就会出现「键值对完全相同但顺序不同时,==返回False」的情况。而转成集合后,因为集合不关心顺序,所以会返回True。
比如这个自定义字典的例子:
class OrderedDictThatCaresAboutOrder(dict): def __eq__(self, other): # 错误地比较了键值对的顺序 return list(self.items()) == list(other.items()) dict1 = OrderedDictThatCaresAboutOrder({"a":1, "b":2}) dict2 = OrderedDictThatCaresAboutOrder({"b":2, "a":1}) print(dict1 == dict2) # False print(set(dict1.items()) == set(dict2.items())) # True
怎么解决你的单元测试问题?
如果是NaN的问题,你可以自定义一个比较函数,专门处理NaN的情况:
import math def dicts_are_equal(d1, d2): if d1.keys() != d2.keys(): return False for key in d1: val1, val2 = d1[key], d2[key] # 处理NaN的情况 if isinstance(val1, float) and isinstance(val2, float): if math.isnan(val1) and math.isnan(val2): continue # 递归处理嵌套字典 if isinstance(val1, dict) and isinstance(val2, dict): if not dicts_are_equal(val1, val2): return False elif val1 != val2: return False return True
如果是自定义字典的问题,你可以把两个字典都转成原生dict再比较:
dict1 = dict(dict_from_api) dict2 = dict(dict_from_postman) print(dict1 == dict2) # 现在会返回True,因为原生dict的==不关心顺序
内容的提问来源于stack exchange,提问作者Albert Rothman

