python 3.7+中list(dict.fromkeys(seq))是保序去重最快写法,但需确保元素可哈希且版本≥3.7;旧版本不保序,不可哈希元素会报错,手动循环应配合set避免o(n²)。

用 dict.fromkeys() 去重最简但要注意 Python 版本
Python 3.7+ 中,dict 保持插入顺序,所以 list(dict.fromkeys(seq)) 是保序去重最快写法。它本质是把原序列作为键塞进空字典——键天然不重复,且顺序由首次出现位置决定。
常见错误现象:dict.fromkeys() 在 Python 3.6 及更早版本中行为不可靠(CPython 3.6 是偶然保序,非语言保证),线上环境若混用旧版本会出错。
- 只适用于元素可哈希的场景(
str、int、tuple等),含list或dict的嵌套结构会报TypeError: unhashable type - 如果原列表有
None、0、False等 falsy 值,不影响,因为这里只用作键,不涉及布尔判断 - 性能上接近 O(n),比循环快 2–3 倍(尤其大数据量)
手动循环 + set 记录已见元素更可控
当你要处理不可哈希元素(比如含 list 的子项),或需要插入手动逻辑(如按某字段去重、跳过空值),就得自己遍历。
典型错误:用 if item not in seen_list: 判断——每次 in 对 list 是 O(n),整体变 O(n²),万级数据就明显卡顿。
- 必须配合
set做 O(1) 查重:seen = set(),循环中用if item not in seen: - 不可哈希时,可转成可哈希表示,例如对字典用
json.dumps(item, sort_keys=True),但要注意浮点精度、NaN、顺序敏感等问题 - 如果去重依据不是整个元素而是某个字段(如
obj.id),就把item.id加入seen,而不是item
list.index() 或 list.count() 去重完全不推荐
有人写 [x for i, x in enumerate(lst) if lst.index(x) == i] 或 if lst.count(x) == 1,这两种都严重误用。
错误现象:前者对每个元素都从头扫描找第一次出现位置,后者每次统计全表频次,时间复杂度都是 O(n²),且 index() 在重复多时还会反复定位,实际比纯循环慢一个数量级。
-
index()不适合做去重判据,它设计用途是查找单个目标,不是去重工具 -
count()更离谱——你只是想确认“是否首次见到”,却硬要数完全部,纯属浪费 - 哪怕列表只有几百项,这种写法在 CPU 敏感场景(如 Web 请求处理)里也会拖慢响应
去重后原地修改还是新建列表?
绝大多数情况应该新建列表。原地修改(如边遍历边 del)容易漏元素或索引越界,而且可读性差、调试困难。
常见误解:以为 lst[:] = dict.fromkeys(lst) 就是“原地”,其实它仍是先建新 dict 再赋值,内存开销没省多少,还掩盖了新建事实。
- 明确区分语义:用
new_lst = list(dict.fromkeys(old_lst))表达“我要一个新有序无重列表” - 如果真要原地(比如函数接收的是外部传入的引用,且明确要求不返回新对象),可用
old_lst[:] = list(dict.fromkeys(old_lst)),但得加注释说明意图 - 注意
dict.fromkeys()返回的是 dict view,必须显式转list(),漏掉这步会得到dict_keys对象而非列表
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











