pin_drop当前位置:知识文库 ❯ 图文
Python列表去重如何保持顺序?90%的人不知道的dict.fromkeys()技巧
大家好啊,我是你们喜欢瞎折腾代码的那个谁。
事情是这样的,前两天我在处理一坨用户行为日志的数据。需求很简单,就是把一堆用户ID去个重,但是呢,顺序千万不能乱,因为ID的顺序代表了用户操作的时间线。我当时想都没想,抬手就是一行:
代码示例
user_ids = [102, 101, 102, 103, 101, 104, 105]
clean_ids = list(set(user_ids))
print(clean_ids)结果输出一看,我人傻了:[101, 102, 103, 104, 105]。顺序全给我打乱了!虽然id是去重了,但这玩意儿传到下一个流程里,领导一看,时间线都对不上,不得把我叼飞?
今天就聊聊Python里这个看似简单、实则暗藏杀机的列表顺序去重问题。全是干货,而且是那种你收藏了下次遇到直接复制就能用的那种。
先甩结论:90%的情况用这一行代码就够了
如果你用的是 Python 3.7+ (现在应该没人用3.6以下的了吧?),而且列表里的元素都是简单的数字、字符串这种可哈希的类型,啥都别想,直接用这行神代码:
代码示例
user_ids = [102, 101, 102, 103, 101, 104, 105]
clean_ids = list(dict.fromkeys(user_ids))
print(clean_ids) # 输出: [102, 101, 103, 104, 105]看到没?102 在最前面,101 在第二位,原来啥顺序,去重后还是啥顺序
原理其实特简单,从Python 3.7开始,字典会记住你插入键的顺序。dict.fromkeys()会用列表的元素当作字典的键,自动把重复的过滤掉,最后再把这个有序字典的键转回列表。完美!
如果你还差点踩坑:不可哈希的"脏数据"
但是!生活总是充满惊喜(惊吓)。我昨天遇到的情况就没那么简单。我那个列表里存的不是单纯的ID,而是字典!
比如这种,每个元素是个字典:
代码示例
log_data = [
{"user_id": 102, "action": "click"},
{"user_id": 101, "action": "view"},
{"user_id": 102, "action": "click"}, # 这玩意儿要删掉
{"user_id": 103, "action": "scroll"},
]
这时候如果你还想用 dict.fromkeys(),Python解释器直接报错给你看:TypeError: unhashable type: 'dict'
咋整?手动撸一个循环吧。
代码示例
import json
def deduplicate_dicts(data_list):
seen = set()
result = []
for item in data_list:
# 把字典转成字符串或者frozenset,这样才能放到set里比较简单点,如果字典结构简单,就转成json字符串
item_key = json.dumps(item, sort_keys=True) # sort_keys保证键的顺序一致
if item_key not in seen:
seen.add(item_key)
result.append(item)
return result调用:
代码示例
clean_log = deduplicate_dicts(log_data)
print(clean_log)输出: [{"user_id": 102, "action": "click"}, {"user_id": 101, "action": "view"}, {"user_id": 103, "action": "scroll"}]
你看,第二个重复的 102 click 就被干掉了,而且顺序保留了第一个出现的位置。
再来点硬核的:海量数据怎么办?
如果你处理的不是上面那种几百条的数据,而是几百万行的日志,内存直接给你干爆。
这时候就得用生成器了。生成器就像个抠门的地主,你要一点,他给你一点,绝不提前多给。
代码示例
def unique_generator(file_path):
seen = set()
with open(file_path, 'r') as f:
for line in f:
line = line.strip()
if line not in seen:
seen.add(line)
yield line # 用yield,每次只吐一行使用的时候:
代码示例
for unique_line in unique_generator('huge_log.txt'):
# 在这里一条一条处理,内存稳稳的
print(unique_line)这种流式处理的方式,别说百万数据,就是TB级的文件也能轻松应对。
性能党看过来
有人纠结到底哪个快?我做了一点点小测试(当然没人家专业网站那么严谨),结论是:
-
list(dict.fromkeys()):代码最优雅,速度极快,首选
-
手写 for 循环 + set:其实更快!因为少了一层字典转换的开销,代码可读性也高
这才是性能王者,而且简单易懂:
代码示例
seen = set()
result = []
for item in huge_list:
if item not in seen:
seen.add(item)
result.append(item)列表推导式那种骚操作:虽然写起来显得很牛逼,但可读性差点,性能也没啥提升,不推荐装这个逼。
总结一下
为了去个重,真的没必要像我最早那样直接用 set() 把顺序丢了。记住这三点:
-
简单数据,要保序:
list(dict.fromkeys(your_list))或者手写循环+set -
列表里有字典/列表:手动遍历,把元素转成可哈希的形式(如json字符串)再判断
-
数据量大到爆内存:用生成器,流式处理
好了,今天的坑我就帮你踩平了。下次去重的时候,别再丢顺序了,不然产品经理提刀来找你,别说我没提醒你!
有啥问题评论区见,我虽然不一定秒回,但看到了一定回。
常见问题
为什么Python的set()会打乱列表顺序?
set()底层使用哈希表实现,元素的存储位置由其哈希值决定,不保证插入顺序。从Python 3.7开始,dict会记住键的插入顺序,所以用dict.fromkeys()可以实现顺序去重。
dict.fromkeys()和手写循环哪个更快?
手写for循环+set其实更快,因为少了一层字典转换的开销。但dict.fromkeys()代码更简洁优雅,对于一般数据量,两者性能差异可忽略。
如何对包含字典的列表进行顺序去重?
字典是不可哈希类型,不能直接放入set。可以使用json.dumps()将字典转为字符串(sort_keys=True保证键顺序一致),或者使用frozenset(item.items())来实现去重判断。
处理超大文件时如何避免内存溢出?
使用生成器配合yield关键字进行流式处理,每次只读取一行数据,去重后立即处理或输出,不需要将所有数据加载到内存中,可以轻松应对TB级文件。
本文涉及AI创作
内容由AI创作,请仔细甄别list快速访问
poll相关推荐
Python元组命名namedtuple
Python元组解包
Python元组index方法
Python元组count方法