collections 是 Python 内置的一个模块,它提供了许多非常有用的、用于替代和增强标准内建容器(如 dict, list, set, tuple)的特化数据容器,这些容器针对特定场景进行了优化,可以让我们写出更简洁、更高效、更健壮的代码。

为什么需要 collections?
标准的 Python 容器功能强大,但在某些特定场景下,它们可能不是最优的选择。
- 字典 (
dict):查找、插入和删除的平均时间复杂度是 O(1),非常高效,但如果你想按元素插入的顺序来遍历它,或者希望字典的键默认有某种特定行为(如自动排序),标准字典就无法满足需求。 - 列表 (
list):作为动态数组,它非常灵活,但如果你需要频繁地在头部或中间插入/删除元素,其时间复杂度是 O(n),效率较低,检查一个元素是否在列表中也需要 O(n) 的时间。
collections 模块正是为了解决这些痛点而设计的。
核心组件详解
collections 模块中最常用的组件包括:namedtuple, deque, Counter, OrderedDict, defaultdict, ChainMap 和 UserDict/UserList/UserString,我们重点讲解前六个。
namedtuple - 命名元组
namedtuple 是一个工厂函数,用于创建一个带有字段名的元组子类,它结合了元组的不可变和轻量级特性,以及字典的通过名称访问字段的便利性。

用途:当你的数据是一个简单的、不可变的对象集合时,使用 namedtuple 比定义一个完整的类更简洁、更高效。
示例:
from collections import namedtuple
# 定义一个命名元组类型 'Point',它有两个字段 'x' 和 'y'
Point = namedtuple('Point', ['x', 'y'])
# 创建实例
p1 = Point(11, y=22)
p2 = Point(1, 2)
# 像元组一样通过索引访问
print(p1[0]) # 输出: 11
# 像对象一样通过属性名访问 (这是最大的优点)
print(p1.x) # 输出: 11
print(p1.y) # 输出: 22
# 它仍然是元组,所以支持所有元组操作
print(p1 + p2) # 输出: Point(x=12, y=24)
# 获取字段名
print(Point._fields) # 输出: ('x', 'y')
# 将命名元组转换为字典
print(p1._asdict()) # 输出: {'x': 11, 'y': 22}
优点:
- 内存高效:比普通对象占用更少的内存。
- 可读性好:
p.x比p[0]更清晰。 - 不可变:保证了数据的安全性。
deque - 双端队列
deque (发音 "deck",即 "double-ended queue" ) 是一个类似列表的容器,但它从两端添加或删除元素都非常快,它在两端都有近似 O(1) 的时间复杂度。

用途:实现高效的队列和栈数据结构,特别是在需要频繁从头部或尾部进行元素操作的场景。
示例:
from collections import deque
# 创建一个 deque
d = deque(['a', 'b', 'c'])
print(d) # 输出: deque(['a', 'b', 'c'])
# 从右侧添加元素
d.append('d')
print(d) # 输出: deque(['a', 'b', 'c', 'd'])
# 从左侧添加元素
d.appendleft('x')
print(d) # 输出: deque(['x', 'a', 'b', 'c', 'd'])
# 从右侧移除元素
d.pop()
print(d) # 输出: deque(['x', 'a', 'b', 'c'])
# 从左侧移除元素
d.popleft()
print(d) # 输出: deque(['a', 'b', 'c'])
# 限制最大长度,当超出时,另一端的数据会被自动移除
d_max = deque(maxlen=3)
d_max.append(1)
d_max.append(2)
d_max.append(3)
print(d_max) # 输出: deque([1, 2, 3])
d_max.append(4)
print(d_max) # 输出: deque([2, 3, 4]) # 1 被自动移除了
优点:
- 高效的两端操作:比
list.pop(0)(O(n)) 快得多。
Counter - 计数器
Counter 是一个 dict 的子类,专门用于计数可哈希对象,它是一个无序的集合,元素是作为键,计数值作为值。
用途:统计一个序列中每个元素出现的次数。
示例:
from collections import Counter
import re
# 统计单词列表
words = ['red', 'blue', 'red', 'green', 'blue', 'blue']
word_counts = Counter(words)
print(word_counts)
# 输出: Counter({'blue': 3, 'red': 2, 'green': 1})
# 访问计数
print(word_counts['blue']) # 输出: 3
print(word_counts['yellow']) # 输出: 0 (不会像普通dict一样报KeyError)
# 获取最常见的n个元素
print(word_counts.most_common(2)) # 输出: [('blue', 3), ('red', 2)]
# 更新计数
word_counts['red'] += 1
print(word_counts['red']) # 输出: 3
# 从一个可迭代对象更新
word_counts.update(['red', 'black'])
print(word_counts) # 输出: Counter({'blue': 3, 'red': 4, 'green': 1, 'black': 1})
# 元素之间的数学运算 ( +, -)
c1 = Counter(a=4, b=2, c=0)
c2 = Counter(a=1, b=2, c=3)
c3 = c1 + c2 # 相加,只保留正数计数
print(c3) # 输出: Counter({'a': 5, 'b': 4, 'c': 3})
c4 = c1 - c2 # 相减,只保留正数计数
print(c4) # 输出: Counter({'a': 3})
优点:
- 简洁易用:一行代码即可完成计数任务。
- 功能丰富:提供
most_common()等实用方法。
OrderedDict - 有序字典
OrderedDict 是 dict 的一个子类,它记住了键值对被插入的顺序,在 Python 3.7 之前,标准的 dict 是无序的,从 Python 3.7 开始,dict 也默认保持插入顺序。OrderedDict 的主要价值在于:
- 兼容旧版 Python:确保代码在 Python 3.6 及更早版本中也能按预期工作。
- 特定操作:
OrderedDict有一些dict没有的方法,如move_to_end()。
用途:需要明确依赖字典插入顺序的场景,或者在旧版 Python 中实现有序字典。
示例:
from collections import OrderedDict
# 创建一个有序字典
od = OrderedDict()
od['a'] = 1
od['b'] = 2
od['c'] = 3
print(od)
# 输出 (在所有Python版本中): OrderedDict([('a', 1), ('b', 2), ('c', 3)])
# 遍历时会按照插入顺序
for key in od:
print(key, end=' ') # 输出: a b c
# 将一个键移动到末尾
od.move_to_end('a')
print("\n", od) # 输出: OrderedDict([('b', 2), ('c', 3), ('a', 1)])
# 将一个键移动到开头
od.move_to_end('a', last=False)
print("\n", od) # 输出: OrderedDict([('a', 1), ('b', 2), ('c', 3)])
# 弹出时也按顺序
print(od.popitem(last=False)) # 输出: ('a', 1)
defaultdict - 默认字典
defaultdict 是 dict 的子类,它为字典的键提供了一个默认的工厂函数,当你访问一个不存在的键时,它会自动调用这个工厂函数来创建一个默认值,而不是抛出 KeyError。
用途:当你希望字典的键自动拥有一个默认值(如列表、0、空集合等)时,可以避免大量的 key in dict 检查和 dict.setdefault() 调用。
示例:
from collections import defaultdict
# 方法1: 使用普通字典,代码繁琐
s = [('yellow', 1), ('blue', 2), ('yellow', 3), ('blue', 4)]
d = {}
for k, v in s:
if k not in d:
d[k] = []
d[k].append(v)
print(d) # 输出: {'yellow': [1, 3], 'blue': [2, 4]}
# 方法2: 使用 defaultdict,代码简洁
d_default = defaultdict(list)
for k, v in s:
d_default[k].append(v) # 如果键不存在,会自动创建一个空列表 []
print(d_default) # 输出: defaultdict(<class 'list'>, {'yellow': [1, 3], 'blue': [2, 4]})
# 计数器是 defaultdict 的一个典型用例
dd_int = defaultdict(int)
dd_int['a'] += 1
dd_int['b'] += 1
dd_int['a'] += 1
print(dd_int) # 输出: defaultdict(<class 'int'>, {'a': 2, 'b': 1})
ChainMap - 链式映射
ChainMap 将多个字典(或其他映射)组合在一起,形成一个单一的、可更新的视图,它本身并不创建一个新的字典,而是将多个字典链接起来,查找操作会按照字典被添加到 ChainMap 中的顺序进行。
用途:将多个配置源(如默认配置、用户配置、命令行参数)合并成一个视图,优先级由链的顺序决定。
示例:
from collections import ChainMap
# 定义几个配置字典
defaults = {'color': 'gray', 'species': 'cat'}
user_prefs = {'color': 'black'}
cli_args = {'color': 'blue'}
# 将它们链接起来,后面的字典优先级更高
config = ChainMap(cli_args, user_prefs, defaults)
# 查找 'color',会从第一个字典开始找,直到找到为止
print(config['color']) # 输出: blue (来自 cli_args)
print(config['species']) # 输出: cat (来自 defaults)
# 添加新键,会添加到第一个字典
config['name'] = 'Whiskers'
print(config['name']) # 输出: Whiskers
print(cli_args) # 输出: {'color': 'blue', 'name': 'Whiskers'}
# 修改一个键,会修改第一个找到的该键
config['color'] = 'purple'
print(config['color']) # 输出: purple
print(cli_args) # 输出: {'color': 'purple', 'name': 'Whiskers'}
# 获取所有底层字典的列表
print(config.maps)
# 输出: [{'color': 'purple', 'name': 'Whiskers'}, {'color': 'black'}, {'color': 'gray', 'species': 'cat'}]
总结与对比
| 数据结构 | 主要用途 | 时间复杂度 | 特点 |
|---|---|---|---|
namedtuple |
创建轻量级的、不可变的数据对象 | 访问 O(1) | 不可变,属性访问,内存高效 |
deque |
高效的两端操作(队列、栈) | 两端操作 O(1) | 比列表在头部操作快得多 |
Counter |
计数可哈希对象 | 插入/计数 O(1) | dict子类,提供most_common() |
OrderedDict |
保持键值对插入顺序 | 查找 O(1) | dict子类,兼容旧版,有move_to_end() |
defaultdict |
为不存在的键提供默认值 | 查找/插入 O(1) | dict子类,避免KeyError,代码更简洁 |
ChainMap |
合并多个映射视图 | 查找 O(N) (N是映射数量) | 不创建新字典,用于合并配置 |
何时使用哪个?
- 需要一个简单的、不可变的“数据类”吗? ->
namedtuple - 需要一个高效的队列或栈吗? ->
deque - 需要统计元素频率吗? ->
Counter - 需要字典保持插入顺序吗?(或在旧版Python中) ->
OrderedDict - 需要字典的键自动有默认值吗? ->
defaultdict - 需要合并多个配置字典吗? ->
ChainMap
掌握 collections 模块是提升 Python 编程能力的重要一步,它能让你在面对各种复杂问题时,拥有更优雅、更高效的解决方案。
