字数 1026,阅读大约需 6 分钟
Python collections 全解析

你有没有遇到过这种情况:统计一个列表里每个元素出现了多少次,写了七八行代码,结果同事一行 Counter 就搞定了?
Python 标准库里的 collections 模块,是那种"不知道的时候觉得没必要,知道了之后天天用"的存在。今天把最常用的三个——Counter、defaultdict、namedtuple——一次性讲透。
Counter:统计这件事,别再手写了
Counter 是专门用来计数的字典子类。最常见的场景:统计词频、统计元素出现次数。
from collections import Counterwords = ["python", "java", "python", "go", "python", "java"]count = Counter(words)print(count)# Counter({'python': 3, 'java': 2, 'go': 1})
不只是列表,字符串也能直接统计:
c = Counter("abracadabra")print(c.most_common(3))# [('a', 5), ('b', 2), ('r', 2)]
most_common(n) 直接返回出现最多的前 n 个,省去排序的麻烦。
Counter 还支持加减运算,两个 Counter 可以直接相加合并,或者相减取差值:
c1 = Counter(a=3, b=2)c2 = Counter(a=1, b=4, c=1)print(c1 + c2) # Counter({'b': 6, 'a': 4, 'c': 1})print(c1 - c2) # Counter({'a': 2}) # 负数自动过滤
什么时候用 Counter?只要你想"统计"两个字,先想 Counter,再想别的。

defaultdict:KeyError 再也不用 try-except 了
普通字典访问不存在的 key 会抛 KeyError,处理起来要么 get(),要么 setdefault(),要么 try-except,总之麻烦。
defaultdict 的思路是:给字典一个"默认工厂",访问不存在的 key 时自动创建默认值。
from collections import defaultdictitems = [("水果", "苹果"), ("蔬菜", "白菜"), ("水果", "香蕉"), ("蔬菜", "菠菜")]grouped = defaultdict(list)for category, item in items: grouped[category].append(item)print(dict(grouped))# {'水果': ['苹果', '香蕉'], '蔬菜': ['白菜', '菠菜']}
如果用普通字典,你得先判断 key 存不存在,再决定是 append 还是初始化列表。用 defaultdict(list) 直接省掉这一步。
常用的默认工厂:
d_int = defaultdict(int) # 默认值 0,适合计数d_list = defaultdict(list) # 默认值 [],适合分组d_set = defaultdict(set) # 默认值 set(),适合去重分组d_str = defaultdict(str) # 默认值 ""
还可以传自定义函数:
d = defaultdict(lambda: "未知")d["name"] = "肥鱼"print(d["name"]) # 肥鱼print(d["age"]) # 未知(自动创建)
注意一个坑:defaultdict 访问不存在的 key 会自动创建,所以 key in d 之后再访问,和直接访问效果不同——直接访问会把 key 写进去。如果你只是想"查一下有没有",用 d.get(key) 更安全。
namedtuple:比字典轻,比类简单
有时候你需要一个简单的数据结构,用字典吧感觉太松散,用类吧又觉得小题大做。namedtuple 就是这个中间地带。
from collections import namedtuplePoint = namedtuple("Point", ["x", "y"])p = Point(3, 4)print(p.x, p.y) # 3 4print(p[0], p[1]) # 3 4(也支持索引访问)print(p) # Point(x=3, y=4)
它本质上是元组,所以是不可变的,也可以解包:
x, y = pprint(x, y) # 3 4
实际项目里,namedtuple 特别适合表示"有固定字段的数据记录",比如数据库查询结果、配置项、坐标点:
Employee = namedtuple("Employee", ["name", "department", "salary"])emp = Employee("张三", "技术部", 15000)print(f"{emp.name} 在 {emp.department},月薪 {emp.salary}")
比字典的好处:字段名固定,不会写错 key;比类的好处:不用写 init,代码更简洁。

namedtuple 还有个 _replace() 方法,可以基于现有实例创建修改了某些字段的新实例(因为元组不可变,所以是"新建"而不是"修改"):
emp2 = emp._replace(salary=18000)print(emp2) # Employee(name='张三', department='技术部', salary=18000)
三者对比,什么时候用哪个
collections 模块里还有 deque(双端队列)、OrderedDict(有序字典,Python 3.7+ 普通字典已有序)等,但上面三个是日常开发里用得最频繁的。
把这三个用熟了,你会发现很多"需要写一堆代码"的场景,其实标准库早就帮你想好了。