collections 与数据容器
本教程共 70 篇 · 第 50 篇 · 更新于 2026-07-22 · 约 5 分钟阅读
50. collections 与数据容器
本节目标:掌握
collections模块中的高级数据结构,在合适场景下替代基础容器,写出更高效的代码。
列表、字典、元组、集合是 Python 的基础容器,日常开发够用了。但在某些场景下,它们不够方便或不够高效。collections 模块提供了一系列扩展数据结构,填补这些空白。
namedtuple:带名字的元组
普通元组通过索引访问,代码可读性差:
point = (3, 4)
print(point[0]) # 3 —— 0 是什么意思?
namedtuple 给每个位置起个名字:
from collections import namedtuple
Point = namedtuple('Point', ['x', 'y'])
p = Point(3, 4)
print(p.x) # 3
print(p.y) # 4
print(p[0]) # 也支持索引访问
namedtuple 本质还是元组,不可变,内存占用和普通元组一样小。它比定义一个完整的类简洁得多:
# 不需要这么写
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
# namedtuple 一行搞定
Point = namedtuple('Point', 'x y')
Tip
namedtuple适合表示「轻量级的结构化数据」:坐标点、颜色 RGB、数据库记录等。如果数据需要方法或可变,还是用普通类。
Python 3.7+ 还提供了 typing.NamedTuple,支持类型注解:
from typing import NamedTuple
class Point(NamedTuple):
x: float
y: float
p = Point(3.0, 4.0)
deque:双端队列
列表的 pop(0) 和 insert(0, x) 操作很慢,因为需要移动所有后续元素。deque(double-ended queue)在两端操作都是 O(1):
from collections import deque
d = deque([1, 2, 3])
d.append(4) # 右侧添加
d.appendleft(0) # 左侧添加
print(d) # deque([0, 1, 2, 3, 4])
d.pop() # 右侧弹出
d.popleft() # 左侧弹出
Note
deque内部用双向链表实现,两端操作极快。但如果你需要频繁访问中间元素,deque不如列表快。按需选择。
限制长度做缓存
d = deque(maxlen=3)
d.append(1)
d.append(2)
d.append(3)
d.append(4)
print(d) # deque([2, 3, 4]) —— 1 被自动挤出
固定长度的 deque 非常适合做「滑动窗口」和「最近 N 条记录」缓存。
Counter:自动计数器
统计元素出现次数,不用手写字典:
from collections import Counter
text = "abracadabra"
counts = Counter(text)
print(counts) # Counter({'a': 5, 'b': 2, 'r': 2, 'c': 1, 'd': 1})
print(counts.most_common(2)) # [('a', 5), ('b', 2)]
Counter 支持数学运算:
c1 = Counter(a=3, b=1)
c2 = Counter(a=1, b=2)
print(c1 + c2) # Counter({'a': 4, 'b': 3})
print(c1 - c2) # Counter({'a': 2})
defaultdict:自动初始化字典
普通字典访问不存在的键会抛 KeyError。defaultdict 会在键不存在时自动创建默认值:
from collections import defaultdict
# 值类型是 list
d = defaultdict(list)
d['fruits'].append('apple')
d['fruits'].append('banana')
print(d) # defaultdict(<class 'list'>, {'fruits': ['apple', 'banana']})
# 值类型是 int(适合做计数)
counts = defaultdict(int)
for word in ['apple', 'banana', 'apple']:
counts[word] += 1
print(counts) # defaultdict(<class 'int'>, {'apple': 2, 'banana': 1})
Warning
defaultdict的default_factory必须是无参可调用对象。如果你写defaultdict(list())(调用了 list),所有键会共享同一个列表对象。正确写法是defaultdict(list)(传入 list 类型本身)。
OrderedDict:保持插入顺序的字典
Python 3.7+ 的普通字典已经保持插入顺序了,OrderedDict 的存在感变弱。但它有两个独特功能:
- 明确表达「顺序重要」的意图:读代码的人一眼就知道这个字典的顺序被依赖。
- 支持重新排序的方法:
from collections import OrderedDict
od = OrderedDict([('a', 1), ('b', 2), ('c', 3)])
od.move_to_end('a') # 把 'a' 移到最后
od.popitem(last=False) # 弹出第一个元素
Note新项目如果只需要「保持插入顺序」,直接用普通
dict就行。OrderedDict只在需要move_to_end()或popitem(last=False)时才派上用场。
ChainMap:多层字典的联合视图
把多个字典「虚拟合并」成一个,不复制数据:
from collections import ChainMap
defaults = {'theme': 'light', 'lang': 'en'}
user_prefs = {'theme': 'dark'}
cm = ChainMap(user_prefs, defaults)
print(cm['theme']) # dark(user_prefs 优先)
print(cm['lang']) # en(从 defaults 找到)
修改 ChainMap 只会影响第一个字典:
cm['font'] = '16px'
print(user_prefs) # {'theme': 'dark', 'font': '16px'}
Tip
ChainMap适合处理「默认值 + 用户配置」的场景,比如命令行参数解析、应用配置合并。比手动copy和update更省内存。
小结
| 数据结构 | 解决的问题 | 对应基础容器 |
|---|---|---|
namedtuple | 元组没有字段名 | tuple |
deque | 列表两端操作慢 | list |
Counter | 手动计数字典啰嗦 | dict |
defaultdict | 键不存在时抛异常 | dict |
OrderedDict | 需要显式顺序控制 | dict |
ChainMap | 多层配置合并 | dict |
collections 模块里的工具不是「必用」,而是「在合适的时候用」。基础容器能解决 80% 的问题,遇到性能瓶颈或代码重复时,再考虑升级。
来源:参考了 liaoxuefeng「collections」、pythondoc「collections 模块」等,改写后所得。