如何使用collections模块中的常用数据结构(defaultdict, Counter, deque)?

狼影
发布: 2025-09-04 18:48:01
原创
618人浏览过
defaultdict、Counter和deque是Python collections模块中高效处理数据分组、计数和双端操作的工具。defaultdict通过自动初始化缺失键提升代码简洁性与效率;Counter专用于可哈希对象的频率统计,提供most_common等便捷方法,适合大数据计数但需注意内存消耗;deque实现O(1)复杂度的双端添加删除,相比list在频繁首尾操作时性能优势显著,尤其适用于队列、栈和滑动窗口场景。三者均能显著提升代码Pythonic程度与执行效率。

如何使用collections模块中的常用数据结构(defaultdict, counter, deque)?

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:
登录后复制
的检查。这会带来额外的条件判断开销,而且代码也会显得更冗长。比如,如果你要分组数据,你会写出类似这样的代码:

即构数智人
即构数智人

即构数智人是由即构科技推出的AI虚拟数字人视频创作平台,支持数字人形象定制、短视频创作、数字人直播等。

即构数智人 36
查看详情 即构数智人
# 普通字典处理
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处理频率统计任务的一把利器,尤其在大数据量场景下,它的优势非常明显,但也并非没有潜在的陷阱。

优势:

  1. 效率高,底层优化:
    Counter
    登录后复制
    是基于C语言实现的字典,它的内部实现经过高度优化。对于大量元素的计数,它比手动编写循环和字典操作要快得多。你不需要担心循环的效率问题,它已经帮你处理好了。
  2. 代码简洁,可读性强: 只需要一行代码,你就能完成对一个序列的计数,例如
    Counter(my_list)
    登录后复制
    。这极大地提高了代码的可读性和开发效率。
  3. 方便的API:
    most_common()
    登录后复制
    方法可以直接获取出现频率最高的元素,
    update()
    登录后复制
    可以方便地进行增量计数,
    elements()
    登录后复制
    可以迭代所有元素(包括重复的),这些都让数据分析变得非常便捷。
  4. 支持集合操作: 它可以像多重集(multiset)一样进行加减、交集、并集等操作,这在处理复杂统计需求时非常有用。

潜在陷阱:

  1. 内存消耗:
    Counter
    登录后复制
    会将所有被计数的唯一元素及其计数存储在内存中。如果你的数据集包含海量的不同的、唯一的元素(例如,数百万个不同的URL字符串),那么
    Counter
    登录后复制
    可能会消耗大量的内存,甚至导致内存溢出。它并不是为处理无限多的唯一项而设计的。
    • 个人思考: 我曾经在处理日志文件时遇到过类似问题,如果只是想统计某个特定字段的Top N,可以考虑流式处理或者结合其他数据结构(如Min-Heap)来限制内存使用,而不是一股脑地把所有唯一项都扔进
      Counter
      登录后复制
  2. 仅限可哈希对象:
    Counter
    登录后复制
    只能计数可哈希的对象。这意味着你不能直接用它来计数列表、字典或其他自定义的不可变对象。如果你需要计数这些对象,你需要先将它们转换为可哈希的形式(例如,将列表转换为元组)。
  3. update()
    登录后复制
    的行为:
    Counter.update()
    登录后复制
    方法是增加计数,而不是覆盖。如果你想重新设置某个元素的计数,你需要直接赋值,而不是用
    update()
    登录后复制
    c = Counter({'a': 1})
    c.update({'a': 5}) # 此时 'a' 的计数会变成 1 + 5 = 6
    print(c) # Counter({'a': 6})
    # 如果想覆盖,需要 c['a'] = 5
    登录后复制
  4. 不是排序结构:
    Counter
    登录后复制
    本身不保证元素的顺序。如果你需要按特定顺序迭代元素,你可能需要先将其转换为列表并进行排序。

总的来说,

Counter
登录后复制
在大多数频率统计场景下都表现出色,但当面对极端大数据量(尤其是有大量唯一项)时,需要警惕其内存占用,并考虑是否有更适合流式处理或分布式计算的工具

deque
登录后复制
在实现高效队列或栈结构时,相比列表有哪些独特优势?

在Python中,我们确实可以用列表(

list
登录后复制
)来模拟队列(
queue
登录后复制
)和栈(
stack
登录后复制
)。例如,
append()
登录后复制
pop()
登录后复制
可以实现栈,
append()
登录后复制
pop(0)
登录后复制
可以实现队列。但当涉及到频繁地在序列两端进行操作时,
deque
登录后复制
(双端队列)相比列表展现出压倒性的独特优势。

deque
登录后复制
的独特优势:

  1. 两端操作的效率:

    • 列表(
      list
      登录后复制
      ):
      append()
      登录后复制
      pop()
      登录后复制
      (从末尾操作)的复杂度是O(1),非常高效。但是,
      insert(0, item)
      登录后复制
      (在开头插入)和
      pop(0)
      登录后复制
      (在开头删除)的复杂度是O(n)。这是因为当你在列表开头插入或删除元素时,Python需要移动所有其他元素来腾出或填充空间。对于大型列表,这会变得非常慢。
    • deque
      登录后复制
      append()
      登录后复制
      appendleft()
      登录后复制
      pop()
      登录后复制
      popleft()
      登录后复制
      这四种操作的复杂度都是O(1)。
      deque
      登录后复制
      的底层实现是一个双向链表,这意味着无论你在哪一端添加或删除元素,都只需要修改少数几个指针,而不需要移动大量数据。
    • 个人观点: 我在处理实时日志流或者实现一个“最近访问记录”的功能时,
      deque
      登录后复制
      的这种O(1)两端操作简直是救命稻草。如果用列表,随着数据量增大,性能瓶颈会很快显现。
  2. 固定大小功能(

    maxlen
    登录后复制
    ):

    • deque
      登录后复制
      提供了一个
      maxlen
      登录后复制
      参数,可以创建一个固定大小的队列。当队列达到最大长度时,如果再添加新元素,最老的元素会自动从另一端被移除。这对于实现滑动窗口、缓存或者限制历史记录大小的场景非常方便,无需手动管理删除旧元素。
    • 列表需要你手动检查长度,并在达到上限时执行
      del my_list[0]
      登录后复制
      ,这又回到了O(n)的性能问题。
  3. 内存效率(在某些操作下):

    • 虽然列表在内存布局上是连续的,通常访问速度快,但在频繁的头部插入/删除操作中,
      deque
      登录后复制
      由于其链表结构,避免了大量元素的复制和移动,反而可能在整体上更节省内存(因为没有额外的临时内存开销用于元素移动)。

总结:

  • 什么时候用
    list
    登录后复制
    如果你需要随机访问(
    my_list[index]
    登录后复制
    )或者主要在列表末尾进行添加/删除操作,并且不涉及频繁的头部操作,那么
    list
    登录后复制
    是更好的选择,因为它在这些方面表现优异,且内存布局紧凑。
  • 什么时候用
    deque
    登录后复制
    当你的核心需求是实现一个高效的队列(FIFO)或栈(LIFO),并且会频繁地在集合的两端进行添加和删除操作时,
    deque
    登录后复制
    是毫无疑问的首选。它的O(1)两端操作特性和
    maxlen
    登录后复制
    功能,能让你写出性能更优、代码更简洁的程序。

以上就是如何使用collections模块中的常用数据结构(defaultdict, Counter, deque)?的详细内容,更多请关注php中文网其它相关文章!

最佳 Windows 性能的顶级免费优化软件
最佳 Windows 性能的顶级免费优化软件

每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。

下载
来源:php中文网
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
最新问题
开源免费商场系统广告
热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板
关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新 English
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送
PHP中文网APP
随时随地碎片化学习

Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号