Pythoncollections模块常用数据结构详解
本文深入解析了Python collections模块中三个核心数据结构:defaultdict、Counter和deque,并提供了实用的使用教程。**defaultdict**通过自动初始化缺失键,显著简化了字典操作,尤其适用于数据分组场景。**Counter**专为可哈希对象的频率统计而设计,其高效的计数功能和便捷的most_common方法,使其在大数据分析中大放异彩,但需注意内存消耗。**deque**则以其O(1)复杂度的双端添加删除操作,成为实现队列、栈和滑动窗口等场景的首选,性能远超列表。掌握这三大工具,能有效提升Python代码的简洁性、可读性和执行效率,助您编写更高效、更Pythonic的代码。
defaultdict、Counter和deque是Python collections模块中高效处理数据分组、计数和双端操作的工具。defaultdict通过自动初始化缺失键提升代码简洁性与效率;Counter专用于可哈希对象的频率统计,提供most_common等便捷方法,适合大数据计数但需注意内存消耗;deque实现O(1)复杂度的双端添加删除,相比list在频繁首尾操作时性能优势显著,尤其适用于队列、栈和滑动窗口场景。三者均能显著提升代码Pythonic程度与执行效率。

Python的collections模块提供了一些非常实用的数据结构,它们在处理特定任务时能让代码更简洁、效率更高。具体来说,defaultdict能优雅地处理字典中不存在的键,Counter则擅长计数可哈希对象,而deque则是一个高效的双端队列,特别适合实现队列和栈。它们都是Python标准库的精华,能帮你写出更“Pythonic”的代码。
解决方案
在实际开发中,我们经常会遇到需要对数据进行分组、计数或管理有序集合的场景。collections模块里的这三位“好帮手”就能大显身手。
defaultdict的使用
设想一下,你正在处理一个数据集,需要将所有拥有相同属性的项归类到一起。如果用普通的字典,每次往一个可能还不存在的键里添加值时,你都得先检查这个键是否存在,如果不存在就得先初始化一个空列表(或集合等),代码看起来会有点啰嗦。
from collections import defaultdict
# 场景:根据水果的颜色进行分类
fruits_by_color_normal = {}
fruit_list = [("apple", "red"), ("banana", "yellow"), ("grape", "purple"), ("strawberry", "red")]
for fruit, color in fruit_list:
if color not in fruits_by_color_normal:
fruits_by_color_normal[color] = []
fruits_by_color_normal[color].append(fruit)
print(f"普通字典分类结果: {fruits_by_color_normal}")
# 使用 defaultdict,代码会简洁很多
fruits_by_color_default = defaultdict(list) # 这里的list是工厂函数,当键不存在时会调用它创建一个空列表
for fruit, color in fruit_list:
fruits_by_color_default[color].append(fruit)
print(f"defaultdict分类结果: {fruits_by_color_default}")
# defaultdict 也可以用于计数
word_counts = defaultdict(int) # 默认值是0
text = "hello world hello python"
for word in text.split():
word_counts[word] += 1
print(f"defaultdict计数结果: {word_counts}")defaultdict的强大之处在于,你指定一个“工厂函数”(比如list、int、set),当访问一个不存在的键时,它会自动调用这个函数来生成一个默认值。这省去了大量的条件判断,让代码逻辑更清晰。
Counter的使用
如果你需要统计某个序列中元素出现的频率,Counter简直是为你量身定做的。它是一个字典的子类,专门用于计数可哈希对象。
from collections import Counter
# 统计列表中元素的频率
data = ['apple', 'banana', 'apple', 'orange', 'banana', 'apple']
fruit_counts = Counter(data)
print(f"水果计数: {fruit_counts}") # Counter({'apple': 3, 'banana': 2, 'orange': 1})
# 统计字符串中字符的频率
char_counts = Counter("programming")
print(f"字符计数: {char_counts}") # Counter({'r': 2, 'g': 2, 'p': 1, 'o': 1, 'a': 1, 'm': 1, 'i': 1, 'n': 1})
# `most_common()` 方法非常实用,可以找出出现频率最高的N个元素
top_2_fruits = fruit_counts.most_common(2)
print(f"出现频率最高的前2种水果: {top_2_fruits}") # [('apple', 3), ('banana', 2)]
# `update()` 方法可以增量更新计数
more_data = ['grape', 'apple', 'grape']
fruit_counts.update(more_data)
print(f"更新后的水果计数: {fruit_counts}") # Counter({'apple': 4, 'banana': 2, 'grape': 2, 'orange': 1})
# 也可以进行简单的集合操作,比如找出共同的元素(交集)
c1 = Counter('aabbc')
c2 = Counter('abbcd')
print(f"交集: {c1 & c2}") # Counter({'a': 1, 'b': 2, 'c': 1})Counter的API设计得非常直观,无论是初始化、更新还是查询,都非常方便。
deque的使用
deque(发音为“deck”,双端队列)是一个列表的替代品,它支持从两端高效地添加和删除元素。如果你的应用场景涉及频繁地在序列两端进行操作,比如实现一个队列、一个栈或者一个固定大小的滑动窗口,deque会比标准列表(list)有显著的性能优势。
from collections import deque
# 创建一个deque
d = deque(['a', 'b', 'c'])
print(f"初始deque: {d}")
# 从右端添加元素
d.append('d')
print(f"append 'd': {d}") # deque(['a', 'b', 'c', 'd'])
# 从左端添加元素
d.appendleft('z')
print(f"appendleft 'z': {d}") # deque(['z', 'a', 'b', 'c', 'd'])
# 从右端删除元素
right_item = d.pop()
print(f"pop() '{right_item}', deque: {d}") # deque(['z', 'a', 'b', 'c'])
# 从左端删除元素
left_item = d.popleft()
print(f"popleft() '{left_item}', deque: {d}") # deque(['a', 'b', 'c'])
# 固定大小的deque(滑动窗口)
fixed_size_history = deque(maxlen=3)
fixed_size_history.append(1)
fixed_size_history.append(2)
fixed_size_history.append(3)
print(f"固定大小deque: {fixed_size_history}") # deque([1, 2, 3], maxlen=3)
fixed_size_history.append(4) # 当添加新元素时,最老的元素会被自动移除
print(f"添加4后: {fixed_size_history}") # deque([2, 3, 4], maxlen=3)
# 旋转操作
d_rotate = deque([1, 2, 3, 4, 5])
d_rotate.rotate(1) # 向右旋转1位
print(f"右旋1位: {d_rotate}") # deque([5, 1, 2, 3, 4])
d_rotate.rotate(-2) # 向左旋转2位
print(f"左旋2位: {d_rotate}") # deque([2, 3, 4, 5, 1])deque的appendleft()和popleft()操作都是O(1)复杂度,而列表的insert(0, item)和pop(0)则是O(n),这意味着对于大量操作,deque的性能优势会非常明显。
defaultdict与普通字典的性能差异体现在哪里?
从表面上看,defaultdict和普通字典(dict)似乎只是在处理缺失键时行为不同,但这种行为差异背后,其实蕴含着性能和代码可读性的考量。我个人觉得,defaultdict的真正价值更多体现在它对代码逻辑的简化上,而不是纯粹的微观性能提升。
当你使用普通字典时,每次尝试访问或修改一个可能不存在的键时,你都需要一个显式的if key not in dict:的检查。这会带来额外的条件判断开销,而且代码也会显得更冗长。比如,如果你要分组数据,你会写出类似这样的代码:
# 普通字典处理
data_groups = {}
for item in some_list:
key = get_key(item)
if key not in data_groups:
data_groups[key] = []
data_groups[key].append(item)而defaultdict则将这种模式封装了起来,让你直接操作,无需关心键是否存在:
# defaultdict 处理
from collections import defaultdict
data_groups = defaultdict(list)
for item in some_list:
key = get_key(item)
data_groups[key].append(item)显然,defaultdict的代码更简洁、更“Pythonic”。从性能角度讲,defaultdict在访问不存在的键时,确实会有一个内部调用工厂函数(比如list())来创建默认值的开销。然而,这个开销通常非常小,在大多数实际应用中,它带来的代码简洁性和可维护性收益远大于这一点点微小的性能损失。
只有在极度性能敏感的场景,且你确定绝大多数键都已存在,或者你明确希望在键不存在时抛出KeyError作为一种验证机制时,普通字典的效率可能会略高一筹,因为它省去了工厂函数的调用。但在日常开发中,defaultdict几乎总是一个更优雅、更推荐的选择,因为它避免了重复的键存在性检查,减少了出错的可能性。
Counter在处理大数据量统计时有哪些优势和潜在陷阱?
Counter无疑是Python处理频率统计任务的一把利器,尤其在大数据量场景下,它的优势非常明显,但也并非没有潜在的陷阱。
优势:
- 效率高,底层优化:
Counter是基于C语言实现的字典,它的内部实现经过高度优化。对于大量元素的计数,它比手动编写循环和字典操作要快得多。你不需要担心循环的效率问题,它已经帮你处理好了。 - 代码简洁,可读性强: 只需要一行代码,你就能完成对一个序列的计数,例如
Counter(my_list)。这极大地提高了代码的可读性和开发效率。 - 方便的API:
most_common()方法可以直接获取出现频率最高的元素,update()可以方便地进行增量计数,elements()可以迭代所有元素(包括重复的),这些都让数据分析变得非常便捷。 - 支持集合操作: 它可以像多重集(multiset)一样进行加减、交集、并集等操作,这在处理复杂统计需求时非常有用。
潜在陷阱:
- 内存消耗:
Counter会将所有被计数的唯一元素及其计数存储在内存中。如果你的数据集包含海量的不同的、唯一的元素(例如,数百万个不同的URL字符串),那么Counter可能会消耗大量的内存,甚至导致内存溢出。它并不是为处理无限多的唯一项而设计的。- 个人思考: 我曾经在处理日志文件时遇到过类似问题,如果只是想统计某个特定字段的Top N,可以考虑流式处理或者结合其他数据结构(如Min-Heap)来限制内存使用,而不是一股脑地把所有唯一项都扔进
Counter。
- 个人思考: 我曾经在处理日志文件时遇到过类似问题,如果只是想统计某个特定字段的Top N,可以考虑流式处理或者结合其他数据结构(如Min-Heap)来限制内存使用,而不是一股脑地把所有唯一项都扔进
- 仅限可哈希对象:
Counter只能计数可哈希的对象。这意味着你不能直接用它来计数列表、字典或其他自定义的不可变对象。如果你需要计数这些对象,你需要先将它们转换为可哈希的形式(例如,将列表转换为元组)。 update()的行为:Counter.update()方法是增加计数,而不是覆盖。如果你想重新设置某个元素的计数,你需要直接赋值,而不是用update()。c = Counter({'a': 1}) c.update({'a': 5}) # 此时 'a' 的计数会变成 1 + 5 = 6 print(c) # Counter({'a': 6}) # 如果想覆盖,需要 c['a'] = 5- 不是排序结构:
Counter本身不保证元素的顺序。如果你需要按特定顺序迭代元素,你可能需要先将其转换为列表并进行排序。
总的来说,Counter在大多数频率统计场景下都表现出色,但当面对极端大数据量(尤其是有大量唯一项)时,需要警惕其内存占用,并考虑是否有更适合流式处理或分布式计算的工具。
deque在实现高效队列或栈结构时,相比列表有哪些独特优势?
在Python中,我们确实可以用列表(list)来模拟队列(queue)和栈(stack)。例如,append()和pop()可以实现栈,append()和pop(0)可以实现队列。但当涉及到频繁地在序列两端进行操作时,deque(双端队列)相比列表展现出压倒性的独特优势。
deque的独特优势:
两端操作的效率:
- 列表(
list):append()和pop()(从末尾操作)的复杂度是O(1),非常高效。但是,insert(0, item)(在开头插入)和pop(0)(在开头删除)的复杂度是O(n)。这是因为当你在列表开头插入或删除元素时,Python需要移动所有其他元素来腾出或填充空间。对于大型列表,这会变得非常慢。 deque:append()、appendleft()、pop()、popleft()这四种操作的复杂度都是O(1)。deque的底层实现是一个双向链表,这意味着无论你在哪一端添加或删除元素,都只需要修改少数几个指针,而不需要移动大量数据。- 个人观点: 我在处理实时日志流或者实现一个“最近访问记录”的功能时,
deque的这种O(1)两端操作简直是救命稻草。如果用列表,随着数据量增大,性能瓶颈会很快显现。
- 列表(
固定大小功能(
maxlen):deque提供了一个maxlen参数,可以创建一个固定大小的队列。当队列达到最大长度时,如果再添加新元素,最老的元素会自动从另一端被移除。这对于实现滑动窗口、缓存或者限制历史记录大小的场景非常方便,无需手动管理删除旧元素。- 列表需要你手动检查长度,并在达到上限时执行
del my_list[0],这又回到了O(n)的性能问题。
内存效率(在某些操作下):
- 虽然列表在内存布局上是连续的,通常访问速度快,但在频繁的头部插入/删除操作中,
deque由于其链表结构,避免了大量元素的复制和移动,反而可能在整体上更节省内存(因为没有额外的临时内存开销用于元素移动)。
- 虽然列表在内存布局上是连续的,通常访问速度快,但在频繁的头部插入/删除操作中,
总结:
- 什么时候用
list? 如果你需要随机访问(my_list[index])或者主要在列表末尾进行添加/删除操作,并且不涉及频繁的头部操作,那么list是更好的选择,因为它在这些方面表现优异,且内存布局紧凑。 - 什么时候用
deque? 当你的核心需求是实现一个高效的队列(FIFO)或栈(LIFO),并且会频繁地在集合的两端进行添加和删除操作时,deque是毫无疑问的首选。它的O(1)两端操作特性和maxlen功能,能让你写出性能更优、代码更简洁的程序。
终于介绍完啦!小伙伴们,这篇关于《Pythoncollections模块常用数据结构详解》的介绍应该让你收获多多了吧!欢迎大家收藏或分享给更多需要学习的朋友吧~golang学习网公众号也会发布文章相关知识,快来关注吧!
126邮箱注册入口及官网地址
- 上一篇
- 126邮箱注册入口及官网地址
- 下一篇
- Greenshot截图无法打开?图文查看问题解决方法
-
- 文章 · python教程 | 25分钟前 |
- Python字符串替换实用技巧分享
- 326浏览 收藏
-
- 文章 · python教程 | 30分钟前 |
- Python日期格式解析与验证技巧
- 220浏览 收藏
-
- 文章 · python教程 | 1小时前 |
- PythonOpenCV像素操作教程
- 362浏览 收藏
-
- 文章 · python教程 | 1小时前 |
- Python条件优化:告别嵌套if-else陷阱
- 147浏览 收藏
-
- 文章 · python教程 | 1小时前 |
- Pandas与NumPyNaN查找区别详解
- 278浏览 收藏
-
- 文章 · python教程 | 1小时前 |
- Python中type函数的作用是什么
- 393浏览 收藏
-
- 文章 · python教程 | 2小时前 |
- 多进程处理大数据的实用技巧
- 330浏览 收藏
-
- 文章 · python教程 | 10小时前 |
- PandasDataFrame列赋值NaN方法解析
- 205浏览 收藏
-
- 文章 · python教程 | 10小时前 |
- Python元组括号用法与列表推导注意事项
- 143浏览 收藏
-
- 文章 · python教程 | 11小时前 |
- ib\_insync获取SPX历史数据教程
- 395浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 485次学习
-
- ChatExcel酷表
- ChatExcel酷表是由北京大学团队打造的Excel聊天机器人,用自然语言操控表格,简化数据处理,告别繁琐操作,提升工作效率!适用于学生、上班族及政府人员。
- 3168次使用
-
- Any绘本
- 探索Any绘本(anypicturebook.com/zh),一款开源免费的AI绘本创作工具,基于Google Gemini与Flux AI模型,让您轻松创作个性化绘本。适用于家庭、教育、创作等多种场景,零门槛,高自由度,技术透明,本地可控。
- 3381次使用
-
- 可赞AI
- 可赞AI,AI驱动的办公可视化智能工具,助您轻松实现文本与可视化元素高效转化。无论是智能文档生成、多格式文本解析,还是一键生成专业图表、脑图、知识卡片,可赞AI都能让信息处理更清晰高效。覆盖数据汇报、会议纪要、内容营销等全场景,大幅提升办公效率,降低专业门槛,是您提升工作效率的得力助手。
- 3410次使用
-
- 星月写作
- 星月写作是国内首款聚焦中文网络小说创作的AI辅助工具,解决网文作者从构思到变现的全流程痛点。AI扫榜、专属模板、全链路适配,助力新人快速上手,资深作者效率倍增。
- 4514次使用
-
- MagicLight
- MagicLight.ai是全球首款叙事驱动型AI动画视频创作平台,专注于解决从故事想法到完整动画的全流程痛点。它通过自研AI模型,保障角色、风格、场景高度一致性,让零动画经验者也能高效产出专业级叙事内容。广泛适用于独立创作者、动画工作室、教育机构及企业营销,助您轻松实现创意落地与商业化。
- 3790次使用
-
- Flask框架安装技巧:让你的开发更高效
- 2024-01-03 501浏览
-
- Django框架中的并发处理技巧
- 2024-01-22 501浏览
-
- 提升Python包下载速度的方法——正确配置pip的国内源
- 2024-01-17 501浏览
-
- Python与C++:哪个编程语言更适合初学者?
- 2024-03-25 501浏览
-
- 品牌建设技巧
- 2024-04-06 501浏览

