Python如何实现列表去重?多种方法性能对比分析

来源:编程网作者:赵六头衔:草根站长
导读:本期聚焦于小伙伴创作的《Python如何实现列表去重?多种方法性能对比分析》,敬请观看详情。保留顺序的同时去掉重复元素,是Python数据处理里容易被做错的一件事。直接用set转换虽然写法最短,却会打乱原有次序,在日志排序、用户行为序列等场景里并不可用。如果既要去重又要保序,常见写法有遍历加判断、字典fromkeys、以及借助集合做已见标记。这三种方式在小列表上差距不明显,但当数据量升至十万级别,耗时可能相差数倍。底层看,列表的in操作是线性扫描,频繁调用会让复杂度升到平方级;而集合的成员判断是哈希查找,接近常量时间。下文通过可运行代码和实测数据,拆解各方案的适用边界与性能拐点,帮你按数据规模选对写法。

在Python开发中,列表去重是一个高频需求,但不同写法在结果顺序、时间开销和内存占用上差异明显。选错方法轻则顺序错乱,重则在大数据量时拖垮接口响应。

Python如何实现列表去重?多种方法性能对比分析

为什么不能直接用set

很多初学者看到去重,第一反应是把列表丢进set,再转回list。这种做法代码只有一行,确实能去掉重复值,但set本身是无序结构,转换过程不会保留元素在原列表中的先后位置。在电商订单轨迹、接口调用流水等强调时序的场景中,顺序丢失会带来业务逻辑错误。

除此之外,set去重会创建新的哈希表,对于超长列表来说有额外内存开销。如果原始列表有五十万个元素,其中重复率很低,set方案和保序方案的内存差距并不大;但若重复率极高,set因为只存唯一值反而更省内存。理解这一点,才能在不同约束下做权衡。

# 无序去重,顺序无法保证
data = [3, 1, 3, 2, 1, 4]
result = list(set(data))
print(result)  # 可能是 [1, 2, 3, 4] 的任意排列

遍历加判断的朴素写法

最直观的保序去重方式,是新建一个空列表,遍历原列表,只有当元素不在新列表中时才追加。逻辑清晰,初学者也容易理解。但它隐藏着一个性能陷阱:判断元素是否已存在时使用了列表的in操作符,而列表底层是数组,in操作需要线性扫描,整体复杂度逼近O(n²)。

当数据量在千级以内,这种写法完全够用,代码可读性也高。可一旦列表长度到了十万,平方级开销就会让脚本卡死。下面的示例展示了基础实现,并附带一段简单计时,方便你直观感受耗时增长。

def dedupe_by_loop(items):
    new_list = []
    for x in items:
        if x not in new_list:  # 线性扫描,数据大时很慢
            new_list.append(x)
    return new_list

data = [i % 1000 for i in range(100000)]
print(len(dedupe_by_loop(data)))

利用字典fromkeys保序去重

从Python 3.7开始,字典正式保证插入顺序。借助dict.fromkeys方法,可以把列表元素作为键传入,由于字典键唯一,重复元素自然被忽略,且保留首次出现顺序。这种方法只需一行,兼顾了简洁与保序。

字典底层同样是哈希表,键的查找是平均O(1),因此整体复杂度是O(n),远比遍历判断快。它唯一的 minor 成本是会临时构建一个字典对象,但相比set,它直接给出保序结果,不需要任何额外判断逻辑。对于绝大多数业务脚本,这是首选写法。

def dedupe_by_dict(items):
    return list(dict.fromkeys(items))

data = ['a', 'b', 'a', 'c', 'b']
print(dedupe_by_dict(data))  # ['a', 'b', 'c']

集合标记法兼顾速度与显式控制

如果希望逻辑更透明,或者需要在去重时附带其他处理,可以用一个set记录已见元素,遍历原列表并判断。set的in操作是哈希查找,速度和外层循环结合后整体仍是O(n),同时代码显式表达了去重意图,方便插入计数、过滤等扩展。

这种写法比fromkeys稍啰嗦,但在复杂流水线里更容易维护。例如你想同时统计每个元素出现次数,只需在判断分支里累加字典即可。性能上,它和fromkeys接近,仅多几次Python层函数调用开销,在十万级数据上差异通常小于毫秒级。

def dedupe_by_seen(items):
    seen = set()
    result = []
    for x in items:
        if x not in seen:
            seen.add(x)
            result.append(x)
    return result

data = [5, 5, 1, 2, 1]
print(dedupe_by_seen(data))  # [5, 1, 2]

三种方案性能实测对比

我们用同一份十万级、重复率约百分之九十的数据,在本地Python环境分别运行三种保序方案与set方案。下表给出近似耗时,实际数值随机器浮动,但量级关系稳定。

方法是否保序十万数据耗时(毫秒)复杂度
set转换12O(n)
遍历加判断3200O(n²)
dict.fromkeys18O(n)
集合标记法22O(n)

从表里能清楚看到,遍历加判断在大数据下完全不可取;set最快但不保序;fromkeys和集合标记法在保序前提下几乎和set一样快。因此日常保序去重直接用dict.fromkeys,需要扩展逻辑再用集合标记法。

特殊元素与注意事项

上述方法都要求列表元素是哈希可类型,例如数字、字符串、元组。如果列表里包含列表或字典这类不可哈希对象,set和dict都会抛出异常。此时只能退回遍历,并用自定义等价判断,比如把子列表转成元组再比较,或者按业务主键去重。

另外在多线程环境修改原列表时,任何去重写法都应先拷贝或确保无人并发写。去重本身不改变元素内容,但重建列表的过程若和写入交错,可能漏掉或重复。简单做法是在去重前用list.copy()拿到快照,再对快照处理。

# 不可哈希元素处理示例
raw = [[1, 2], [1, 2], [3, 4]]
seen = set()
out = []
for item in raw:
    key = tuple(item)  # 转成可哈希的元组
    if key not in seen:
        seen.add(key)
        out.append(item)
print(out)  # [[1, 2], [3, 4]]

总结与选型建议

回到开头的问题,Python列表去重并不是只有一种答案。若完全不在意顺序,用set最省事;若必须保序且逻辑简单,dict.fromkeys一行解决;若去重同时要干别的事,集合标记法最灵活;而遍历判断仅适合极小数据或教学演示。

在真实项目里,我建议默认封装一个dedupe函数,内部用fromkeys实现,并在文档注明不保证对不可哈希元素的支持。这样业务侧调用统一,后续若发现性能瓶颈再针对数据类型切换策略,不至于让去重写法散落各处难以维护。

Python列表去重性能对比修改时间:2026-08-04 16:09:34

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。