Python collections 模块核心知识
collections 模块是 Python 的一个内置模块,它提供了标准数据类型(如 dict, list, set, tuple)的替代品,这些替代品为特定场景提供了更高性能或更方便的功能。可以把它们看作是处理数据的“瑞士军刀”。
collections 模块概览
我们将逐一深入探讨以下几个核心的数据结构:
namedtuple: 带字段名的元组,增强代码可读性。deque: 高效实现两端插入和删除的双端队列。Counter: 用于计数的字典子类。defaultdict: 带有默认值的字典,处理缺失键的利器。OrderedDict: 记住键插入顺序的字典。ChainMap: 将多个字典链接成一个单一视图。UserDict,UserList,UserString: 用于创建自定义容器的包装类。
namedtuple:命名元组
核心功能
创建一个行为像元组但字段可以用名字访问的类。它解决了普通元组通过索引访问(如 data[0])时可读性差的问题。
优点
- 可读性强:通过名称访问字段(
point.x),代码更清晰。 - 轻量级:和元组一样是不可变类型,内存占用小,效率高。
- 向后兼容:仍然支持索引和迭代,像普通元组一样使用。
使用方法
namedtuple 是一个工厂函数,它返回一个新的类。
from collections import namedtuple
# 1. 定义一个 namedtuple 类型
# 第一个参数是类型名,第二个参数是字段名的字符串或列表
Point = namedtuple('Point', ['x', 'y'])
# 或者使用空格分隔的字符串:Point = namedtuple('Point', 'x y')
# 2. 创建实例
p = Point(10, 20)
# 3. 访问数据
print(f"通过名称访问: x={p.x}, y={p.y}")
print(f"通过索引访问: x={p[0]}, y={p[1]}")
# 4. namedtuple 的特性
x, y = p # 支持解包
print(f"解包后: x={x}, y={y}")
print(f"p 是一个元组的子类: {isinstance(p, tuple)}")
# 常用辅助方法
# _make(iterable): 从可迭代对象创建实例
data = [100, 200]
p2 = Point._make(data)
print(f"从列表创建: {p2}")
# _asdict(): 将 namedtuple 转换为一个 OrderedDict
print(f"转换为字典: {p2._asdict()}")
应用场景
- 定义没有复杂方法的轻量级、不可变的数据结构,如数据库查询返回的记录、CSV 文件中的一行数据。
- 替代字典来存储固定字段的数据,以节省内存。
deque:双端队列
核心功能
deque(发音为 "deck",是 "double-ended queue" 的缩写)是一个线程安全、内存高效的双端队列。它支持从两端快速地添加(append)和弹出(pop)元素。
与 list 的对比
list在末尾添加/删除元素(append,pop)是 O(1) 的,但在开头添加/删除(insert(0, ...),pop(0))是 O(n) 的,因为需要移动所有后续元素。deque在两端添加/删除元素(append,pop,appendleft,popleft)都是 O(1) 的。
使用方法
from collections import deque
# 1. 创建 deque
d = deque(['b', 'c', 'd'])
print(f"初始 deque: {d}")
# 2. 从两端添加元素
d.append('e') # 右侧添加
d.appendleft('a') # 左侧添加
print(f"添加后: {d}")
# 3. 从两端删除元素
right_item = d.pop() # 右侧弹出
left_item = d.popleft() # 左侧弹出
print(f"弹出后: {d}, 弹出的元素: 左='{left_item}', 右='{right_item}'")
# 4. 限制长度 (maxlen)
# 创建一个最大长度为3的deque,当元素超过3个时,旧的元素会自动从另一端被丢弃
history = deque(maxlen=3)
history.append(1)
history.append(2)
history.append(3)
print(f"History: {history}")
history.append(4) # 添加4后,最左边的1被丢弃
print(f"添加4后: {history}")
history.appendleft(0) # 从左边添加0,最右边的4被丢弃
print(f"添加0后: {history}")
# 5. 旋转 (rotate)
d = deque(['a', 'b', 'c', 'd'])
d.rotate(1) # 向右旋转1位
print(f"向右旋转1位: {d}")
d.rotate(-2) # 向左旋转2位
print(f"向左旋转2位: {d}")
应用场景
- 实现队列(Queue):先进先出(FIFO),使用
append()和popleft()。 - 实现栈(Stack):后进先出(LIFO),使用
append()和pop()。 - 保留历史记录:使用
maxlen参数,例如保存最近的 N 个操作或日志。 - 广度优先搜索(BFS):在图算法中常用作队列。
Counter:计数器
核心功能
一个特殊的字典子类,用于计算可哈希对象的出现次数。键是元素,值是元素的数量。
优点
- 方便:一行代码即可完成计数。
- 功能丰富:提供了
most_common()等实用方法。 - 支持运算:可以对两个
Counter对象进行加、减、交、并等集合运算。
使用方法
from collections import Counter
# 1. 创建 Counter
# 从字符串创建
c1 = Counter('abracadabra')
print(f"从字符串计数: {c1}")
# 从列表创建
c2 = Counter(['apple', 'orange', 'apple', 'banana', 'apple'])
print(f"从列表计数: {c2}")
# 2. 访问和更新计数
print(f"苹果的数量: {c2['apple']}")
print(f"梨子的数量 (不存在的键返回0): {c2['pear']}")
c2['pear'] += 1 # 可以直接更新
print(f"更新后梨子的数量: {c2['pear']}")
# 3. 最常用的方法: most_common(n)
# 返回一个列表,包含n个最常见的元素及其计数,按计数从高到低排序
print(f"最常见的2个元素: {c1.most_common(2)}")
# 4. 元素迭代器: elements()
# 返回一个迭代器,其中每个元素重复其计数的次数
sorted_elements = sorted(c1.elements())
print(f"所有元素展开: {''.join(sorted_elements)}")
# 5. 算术运算
c_a = Counter(a=3, b=1)
c_b = Counter(a=1, b=2)
print(f"c_a + c_b = {c_a + c_b}") # 加法:各项计数相加
print(f"c_a - c_b = {c_a - c_b}") # 减法:只保留正数结果
print(f"c_a & c_b = {c_a & c_b}") # 交集:取最小计数 min(c_a[x], c_b[x])
print(f"c_a | c_b = {c_a | c_b}") # 并集:取最大计数 max(c_a[x], c_b[x])
应用场景
- 统计文本中单词或字符的频率。
- 数据分析中计算各类别的数量。
- 解决算法问题,如“找到出现次数最多的元素”。
defaultdict:默认值字典
核心功能
defaultdict 是 dict 的一个子类,它在创建时接受一个“默认工厂”(default factory)函数。当访问一个不存在的键时,它不会抛出 KeyError,而是调用这个工厂函数为该键创建一个默认值。
与 dict 的对比
使用普通字典处理不存在的键通常需要 try...except 或 if key in d: 判断,defaultdict 则简化了这个过程。
使用方法
default_factory 必须是一个可调用对象,如 int, list, set, lambda: 'default'。
from collections import defaultdict
# 示例1:按首字母对单词进行分组
words = ['apple', 'ant', 'ball', 'bat', 'cat']
# 使用 list 作为默认工厂,当键不存在时,默认值是一个空列表 []
grouped_words = defaultdict(list)
for word in words:
first_letter = word[0]
grouped_words[first_letter].append(word)
print(f"按首字母分组: {grouped_words}")
# 注意:它仍然是一个字典,只是行为有所不同
print(f"grouped_words['a'] = {grouped_words['a']}")
# print(grouped_words['z']) # 访问不存在的键 'z',会创建一个空列表并返回
# 示例2:计数(替代 Counter 的一种方式)
# 使用 int 作为默认工厂,当键不存在时,默认值是 int() 的返回值,即 0
word_counts = defaultdict(int)
for word in ['apple', 'orange', 'apple']:
word_counts[word] += 1
print(f"单词计数: {word_counts}")
print(f"苹果的数量: {word_counts['apple']}")
print(f"梨子的数量: {word_counts['pear']}") # 访问 'pear',默认值为0
应用场景
- 分组:将一系列项目根据某个属性分组到列表中(
defaultdict(list))。 - 计数:对项目进行计数(
defaultdict(int))。 - 构建图:用
defaultdict(set)来表示邻接表,每个节点的邻居存储在一个集合中。
OrderedDict:有序字典
核心功能
OrderedDict 是一个记住键插入顺序的字典。
重要提示
从 Python 3.7 开始,内置的 dict 类型也保证了插入顺序。因此,OrderedDict 的重要性有所下降。但在以下场景中,它仍然有用:
- 向后兼容:需要在 Python 3.6 或更早版本中保持顺序。
- 顺序敏感的等价性:两个
OrderedDict只有在内容和顺序都相同时才被认为是相等的。而普通dict从 Python 3.7 开始,只要内容相同就相等。 - 特定方法:
OrderedDict提供了move_to_end()方法,可以方便地将一个键移动到开头或结尾。
使用方法
from collections import OrderedDict
# 1. 创建 OrderedDict
od = OrderedDict()
od['a'] = 1
od['c'] = 3
od['b'] = 2
print(f"OrderedDict: {od}") # 输出: OrderedDict([('a', 1), ('c', 3), ('b', 2)])
# 2. move_to_end(key, last=True)
# 将键 'a' 移动到末尾
od.move_to_end('a')
print(f"将 'a' 移动到末尾: {od}")
# 将键 'c' 移动到开头 (last=False)
od.move_to_end('c', last=False)
print(f"将 'c' 移动到开头: {od}")
# 3. popitem(last=True)
# 默认弹出末尾的项 (LIFO)
item = od.popitem()
print(f"弹出末尾项: {item}, 剩余: {od}")
# 弹出开头的项 (FIFO)
od.move_to_end('b', last=False) # 先把b移到开头
item_first = od.popitem(last=False)
print(f"弹出开头项: {item_first}, 剩余: {od}")
应用场景
- LRU 缓存(Least Recently Used Cache):
move_to_end()非常适合实现 LRU 缓存。当一个项目被访问时,就把它移到末尾表示“最近使用”。当缓存满时,从开头删除“最久未使用”的项目。 - 需要顺序敏感比较的场景。
ChainMap:链式映射
核心功能
ChainMap 可以将多个字典(或其他映射)组合在一起,创建一个单一、可更新的视图。查找会按顺序搜索底层的映射,直到找到键为止。
关键行为
- 读取:从第一个字典开始查找,找到即返回。
- 写入/更新/删除:只对第一个字典进行操作。
使用方法
from collections import ChainMap
# 场景:模拟配置,有默认配置和用户自定义配置
default_config = {'theme': 'dark', 'font_size': 12}
user_config = {'font_size': 14, 'show_toolbar': True}
# ChainMap 会先在 user_config 中查找,再在 default_config 中查找
config = ChainMap(user_config, default_config)
print(f"字体大小: {config['font_size']}") # 14 (来自 user_config)
print(f"主题: {config['theme']}") # 'dark' (来自 default_config)
print(f"显示工具栏: {config['show_toolbar']}") # True (来自 user_config)
# print(config['language']) # KeyError,因为两个字典都没有
# 写入操作只影响第一个字典
config['font_size'] = 16
print(f"更新后的 user_config: {user_config}")
print(f"default_config 保持不变: {default_config}")
# 添加新的上下文层级 (例如,项目特定配置)
project_config = {'theme': 'light'}
new_config = config.new_child(project_config)
print(f"新配置的主题: {new_config['theme']}") # 'light' (来自 project_config)
print(f"新配置的字体大小: {new_config['font_size']}") # 16 (来自 user_config)
应用场景
- 管理作用域:模拟编程语言中的嵌套作用域,如全局变量、局部变量等。
- 配置管理:管理多层配置,如默认配置、用户配置、项目配置。
- 模板引擎:提供上下文数据。
UserDict, UserList, UserString
核心功能
这些是 dict, list, str 的包装类,旨在让用户更容易地创建自己的容器子类。
为什么不直接继承 dict, list, str?
直接继承内置类型有时会遇到一些实现上的复杂问题(因为它们很多是用 C 语言实现的)。User* 类将数据存储在一个内部属性中(如 self.data),使得重写方法变得更简单、更安全。
使用示例
创建一个在设置值时自动转为大写的字典。
from collections import UserDict
class UpperCaseDict(UserDict):
def __setitem__(self, key, value):
# 将值转换为字符串并转为大写
super().__setitem__(key, str(value).upper())
ucd = UpperCaseDict()
ucd['name'] = 'Alice'
ucd['number'] = 123
print(ucd) # 输出: {'name': 'ALICE', 'number': '123'}
应用场景
当你需要一个行为略有不同的字典、列表或字符串,并且希望以一种干净的方式实现它时。
总结表
| 类 (Class) | 核心功能 | 主要应用场景 |
|---|---|---|
namedtuple |
带字段名的元组 | 定义轻量级、不可变的数据结构,提高代码可读性。 |
deque |
高效的双端队列 | 实现队列、栈,保存历史记录(maxlen),图的BFS。 |
Counter |
计数器字典 | 统计元素频率,查找最常见元素,进行计数相关的集合运算。 |
defaultdict |
带默认值的字典 | 分组数据到列表/集合,避免 KeyError,简化计数代码。 |
OrderedDict |
有序字典 | 实现LRU缓存,或在Python 3.7之前需要有序字典的场景。 |
ChainMap |
链式映射 | 管理多层配置(如默认配置+用户配置),模拟作用域。 |
User* |
容器包装类 | 方便、安全地创建自定义的字典、列表或字符串子类。 |

浙公网安备 33010602011771号