Python 的 collections 模块提供了几个”比内置更高级”的数据结构。它们不是替代品,而是在特定场景下让代码更清晰、更快 。前面几篇零散提到过 Counter、defaultdict、deque、namedtuple——这一篇把 collections 里六大主角一次讲完。
一、模块概览
类
用途
Counter
计数专用字典
defaultdict
自动创建缺失 key 的字典
OrderedDict
有序字典(3.7+ 后普通 dict 也保序)
deque
双端队列,两头 O(1) 操作
namedtuple
带字段名的元组
ChainMap
多个字典的链式视图
二、Counter:计数专用字典 前面 字典 Dict 的使用 讲过:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 from collections import Counterwords = ["a" , "b" , "a" , "c" , "b" , "a" ] c = Counter(words) print (c) Counter("mississippi" ) c.most_common(2 ) c["z" ] c.total() c.update("aab" ) c.subtract("bb" )
支持数学运算 (元素级):
1 2 3 4 5 6 7 a = Counter(a=3 , b=1 ) b = Counter(a=1 , b=2 , c=5 ) a + b a - b a & b a | b
用途 :
统计词频、字符频、访问量;
找 Top N;
集合的”多重集合”运算。
三、defaultdict:分组神器 自动创建缺失 key,无需先检查:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 from collections import defaultdictgroups = defaultdict(list ) for name, dept in employees: groups[dept].append(name) counter = defaultdict(int ) for word in words: counter[word] += 1 matrix = defaultdict(lambda : defaultdict(int )) matrix["a" ]["b" ] += 1
参数 是”如何创建缺失值”的可调用对象:
int → 缺省 0
list → 缺省空 list
set → 缺省空 set
lambda: ... → 任意逻辑
访问不存在的 key 会自动创建 (如果不想触发,用 d.get(k) 而不是 d[k])。
四、OrderedDict:需要顺序还得靠它的时候 Python 3.7+ 普通 dict 已经保持插入顺序,但 OrderedDict 还有一些独有能力:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 from collections import OrderedDictod = OrderedDict() od["a" ] = 1 od["b" ] = 2 od["c" ] = 3 od.move_to_end("a" ) od.move_to_end("b" , last=False ) od.popitem(last=True ) od.popitem(last=False ) OrderedDict(a=1 , b=2 ) == OrderedDict(b=2 , a=1 ) dict (a=1 , b=2 ) == dict (b=2 , a=1 )
典型应用 :手写 LRU 缓存。
五、deque:双端队列 一个”两端都能 O(1) 增删”的容器。前面 列表 List 详解 提到过:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 from collections import dequeq = deque() q.append(1 ) q.append(2 ) q.appendleft(0 ) print (q) q.pop() q.popleft() tail = deque(maxlen=3 ) for i in range (10 ): tail.append(i) print (tail) q = deque([1 , 2 , 3 , 4 , 5 ]) q.rotate(2 ) q.rotate(-1 )
用途 :
队列 / 双端队列;
滑动窗口 / 保留最近 N 个;
BFS(广度优先搜索);
循环缓冲。
六、namedtuple:给元组起名字 1 2 3 4 5 6 7 8 9 10 11 12 from collections import namedtuplePoint = namedtuple("Point" , ["x" , "y" ]) p = Point(3 , 4 ) p.x, p.y p[0 ], p[1 ] x, y = p Point._fields p._replace(x=10 ) p._asdict()
更现代的 typing.NamedTuple 支持类型注解和方法:
1 2 3 4 5 6 7 8 from typing import NamedTupleclass Point (NamedTuple ): x: float y: float def distance (self, other ): return ((self .x - other.x) ** 2 + (self .y - other.y) ** 2 ) ** 0.5
namedtuple vs dataclass :
namedtuple:不可变、可解包、可索引、内存小;
dataclass:可变(或 frozen 可选)、可加复杂方法、更灵活。
小、只读、频繁创建 → namedtuple;有方法/可变 → dataclass。
七、ChainMap:多个字典的链式视图 想模拟”多层配置”(默认 → 用户 → 环境变量),一层层查找:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 from collections import ChainMapdefaults = {"color" : "red" , "size" : "M" , "font" : "Arial" } user = {"color" : "blue" } env = {"size" : "L" } config = ChainMap(env, user, defaults) print (config["color" ]) print (config["size" ]) print (config["font" ]) config["color" ] = "green" print (env)
用途 :配置层叠、变量作用域模拟。
八、Counter 常见套路 Top N 1 Counter(words).most_common(10 )
找出出现 1 次的元素 1 [k for k, v in Counter(items).items() if v == 1 ]
判断两个序列元素相同(不考虑顺序和重复) 1 Counter(a) == Counter(b)
找出多的元素 1 diff = Counter(list1) - Counter(list2)
九、defaultdict 常见套路 反向索引 1 2 3 index = defaultdict(list ) for i, tag in enumerate (tags): index[tag].append(i)
图的邻接表 1 2 3 4 graph = defaultdict(set ) for u, v in edges: graph[u].add(v) graph[v].add(u)
两级分组 1 2 3 groups = defaultdict(lambda : defaultdict(list )) for record in records: groups[record["region" ]][record["type" ]].append(record)
十、deque 常见套路 滑动窗口最大值(单调队列) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 from collections import dequedef sliding_max (nums, k ): q = deque() result = [] for i, x in enumerate (nums): while q and q[0 ] <= i - k: q.popleft() while q and nums[q[-1 ]] < x: q.pop() q.append(i) if i >= k - 1 : result.append(nums[q[0 ]]) return result print (sliding_max([1 , 3 , -1 , -3 , 5 , 3 , 6 , 7 ], 3 ))
BFS 1 2 3 4 5 6 7 8 9 10 11 12 from collections import dequedef bfs (graph, start ): visited = {start} queue = deque([start]) while queue: node = queue.popleft() yield node for neighbor in graph[node]: if neighbor not in visited: visited.add(neighbor) queue.append(neighbor)
十一、UserDict / UserList / UserString 想自定义 dict/list/str 但不想踩”直接继承内置类型”的坑:
1 2 3 4 5 6 7 8 9 10 from collections import UserDictclass LoggedDict (UserDict ): def __setitem__ (self, key, value ): print (f"设置 {key} ={value} " ) super ().__setitem__(key, value) d = LoggedDict() d["a" ] = 1
直接 class LoggedDict(dict) 也能用,但内置方法可能不走你的 __setitem__ ——比如 dict.update 就跳过。UserDict 是”纯 Python 实现”,所有操作都过你重写的方法。
十二、性能对比
defaultdict vs dict.setdefault :性能相近,defaultdict 稍快,代码更漂亮;
Counter vs 手写循环 + dict :Counter 稍慢一点,但简洁得多;
deque vs list :两端操作 deque 快 100 倍;随机索引 list 快;
namedtuple vs dataclass :namedtuple 更快、更省内存;dataclass 更灵活。
十三、一个完整例子:URL 访问统计 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 """日志分析工具,展示 Counter / defaultdict / deque / namedtuple 的组合。""" from collections import Counter, defaultdict, deque, namedtuplefrom datetime import datetimeLogEntry = namedtuple("LogEntry" , ["ts" , "ip" , "path" , "status" , "elapsed_ms" ]) def parse_log (line: str ) -> LogEntry | None : """ 简化格式: 2025-09-27T10:00:00 1.2.3.4 GET /index 200 15 """ parts = line.split() if len (parts) != 6 : return None return LogEntry( ts=datetime.fromisoformat(parts[0 ]), ip=parts[1 ], path=parts[3 ], status=int (parts[4 ]), elapsed_ms=int (parts[5 ]), ) def analyze (lines ): ip_counter = Counter() path_by_status = defaultdict(Counter) recent = deque(maxlen=100 ) slow = deque(maxlen=5 ) for line in lines: entry = parse_log(line) if entry is None : continue ip_counter[entry.ip] += 1 path_by_status[entry.status][entry.path] += 1 recent.append(entry) slow.append(entry) slow = deque(sorted (slow, key=lambda e: e.elapsed_ms, reverse=True )[:5 ], maxlen=5 ) return { "top_ips" : ip_counter.most_common(10 ), "errors_by_path" : dict (path_by_status[500 ].most_common(5 )), "recent" : list (recent)[-5 :], "slowest" : list (slow), } if __name__ == "__main__" : sample = [ "2025-09-27T10:00:00 1.2.3.4 GET /a 200 12" , "2025-09-27T10:00:01 1.2.3.4 GET /b 200 20" , "2025-09-27T10:00:02 5.6.7.8 GET /a 500 150" , "2025-09-27T10:00:03 5.6.7.8 GET /c 200 500" , ] print (analyze(sample))
十四、常见陷阱
defaultdict 触发副作用 :d[k] 会创建 key,即使你只想”查”。用 d.get(k) 只查。
Counter 减法保留正数 :如果想保留所有(含负),用 c.subtract(other)。
deque(maxlen=N) :加入超出的元素时对端会被丢弃 。别忘了。
namedtuple 不可变 :改字段用 ._replace() 返回新对象。
OrderedDict 相等比较 :顺序不同就 !=。
ChainMap 修改 :永远动第一个 dict。想改深层要指定:chain.maps[i][k] = ...。
十五、小结与延伸阅读
Counter :计数、Top N、多重集合运算;
defaultdict :分组、计数、嵌套结构;
OrderedDict :需要 move_to_end / popitem(last) 时用;
deque :两端 O(1)、限长、滑动窗口、BFS;
namedtuple :轻量记录,比字典更结构化,比 class 更省;
ChainMap :多层配置查找;
UserDict/UserList/UserString :更安全的继承。
延伸阅读:
下一篇 常用标准库:itertools 与 functools 我们讲两个函数式工具库。