python的set去重可靠但非绝对安全,依赖对象可哈希性及__hash__与__eq__一致性;常见问题源于可变对象、自定义类未正确定义、嵌套结构类型混用及浮点精度等。

Python 的 set 去重是靠谱的,但“靠谱”不等于“无条件安全”——它在绝大多数场景下正确高效,可一旦忽略其底层行为和隐含约束,就可能引发不易察觉的副作用。
去重原理可靠:基于哈希与相等性判断
set 去重依赖两个核心机制:对象必须可哈希(hashable),且去重逻辑由 __hash__ 和 __eq__ 共同决定。内置类型如 int、str、tuple(不含不可哈希元素)都满足要求,因此对它们去重完全可靠。
- 重复元素被自动合并,结果顺序不保证(Python 3.7+ 保持插入顺序仅限于
dict,set仍无序) - 时间复杂度平均为 O(1) 插入,整体去重接近 O(n),效率高
- 不是“简单删掉后面出现的”,而是通过哈希桶+链地址法真正判重
常见副作用来源:可变对象与自定义类
问题通常不出在 set 本身,而出在用户传入的数据上。
-
列表、字典、集合本身不可哈希:直接
set([ [1], [2], [1] ])会报TypeError: unhashable type: 'list' -
自定义类未正确定义
__hash__和__eq__:若只重写__eq__而没设__hash__ = None或提供一致哈希,可能造成逻辑矛盾——两个==为True的实例却因哈希不同被同时存入set - 可变对象中途被修改:把一个列表转成元组再进 set 是安全的;但如果把可变对象(如自定义类实例)加入 set 后又修改其影响哈希或相等性的属性,该 set 就进入“损坏状态”——既无法再被正确查找,也破坏了内部结构
嵌套结构与类型混用需手动处理
set 不会递归去重,也不理解“语义重复”。例如:
立即学习“Python免费学习笔记(深入)”;
-
set([(1, 2), [1, 2]])合法(元组和列表类型不同),但二者“内容相同”不会被识别为重复 -
set([1, 1.0, True])在 Python 中实际得到{1},因为1 == 1.0 == True且它们哈希值相同——这是设计使然,但若业务要求区分数字类型,就得先统一类型或用其他方式判重 - 含浮点数时要注意精度:
0.1 + 0.2 != 0.3,可能导致本应去重的数值被保留
替代方案建议:按需选择更稳的去重逻辑
当默认 set 行为不符合业务语义时,不要硬改数据去适配,而应换思路:
- 需要保持原顺序?用
dict.fromkeys(seq)(Python 3.7+)或手动遍历+in判断 - 要按字段去重对象列表?用
set()记录已见的(obj.field1, obj.field2)元组,而非直接塞对象 - 需容忍浮点误差?先用
round(x, 5)或math.isclose预处理,再进 set - 处理不可哈希数据?转成 JSON 字符串或
repr()(注意稳定性与安全性)后再哈希
set 去重本身很扎实,出问题的地方往往在输入数据的可控性与业务语义的理解上。看清“它按什么判重”,比记住“它能去重”更重要。










