Python collections模块详解

很多Python开发者刚入门时,用的都是内置的dictlisttuple这些通用容器,但当场景变复杂——比如做快速双端插入、优雅的缺失键处理、精准的元素计数——内置容器要么效率不足,要么代码会变得啰嗦混乱。

这时候Python标准库的宝藏模块collections就能直接救场!它内置了7+种针对特定场景优化的集合类,既保持了和内置容器的兼容性,又加了不少实用功能。今天我们挑核心、高频的来逐一拆解。


1. namedtuple:带名字的不可变元组

普通元组的问题是元素只能靠索引访问,代码过段时间看根本记不住索引0是啥、索引1是啥。namedtuple就是为了解决这个痛点的:它是tuple的子类,每个字段都有专属的属性名,同时保留了元组的不可变、内存高效等特性。

基本用法

from collections import namedtuple

# 定义格式1:字段用列表/元组
Point = namedtuple("Point", ["x", "y"])
# 定义格式2:字段用空格分隔的字符串(更简洁)
Student = namedtuple("Student", "name age grade")

# 实例化和普通元组一样,但可以用属性访问
p = Point(1.2, 3.4)
s = Student("小明", 10, 4)

print(p.x)  # 输出: 1.2
print(s.grade)  # 输出: 4

核心特点

  1. 完全兼容元组:可以用索引、切片、len()infor循环等所有元组操作
  2. 内存效率极高:和普通元组几乎没有区别,比自定义轻量类快且省内存
  3. 不可变:属性和值都不能修改

进阶小技巧

# 1. 添加默认值(Python 3.7+ 更推荐用defaults参数)
Point = namedtuple("Point", ["x", "y", "z"], defaults=[0, 0])  # 最后两个字段有默认值
p1 = Point(5)  # 输出: Point(x=5, y=0, z=0)

# 2. 从序列/字典批量创建实例
coords = (7, 8, 9)
p2 = Point._make(coords)  # 用_make(),参数必须是长度匹配的可迭代对象
info = {"name": "小红", "age": 11, "grade": 5}
s2 = Student(**info)  # 用**解包字典

# 3. 支持类型注解(Python 3.6+ 推荐用typing.NamedTuple类语法)
from typing import NamedTuple

class TypedPoint(NamedTuple):
    x: float
    y: float = 0.0  # 也可以直接在类定义里加默认值

tp = TypedPoint(2.5)
print(tp)  # 输出: TypedPoint(x=2.5, y=0.0)

2. deque:双端队列(Queue/Stack的优化版)

list虽然也能模拟队列(从头部insert(0)、尾部append())和栈(从尾部append()pop()),但从头部插入/删除的复杂度是O(n)——元素多的时候会非常慢。

deque(全称double-ended queue)是专门为两端快速操作设计的:左右两端的append/pop复杂度都是O(1),而且线程安全!

基本用法

from collections import deque

# 初始化
d = deque(["a", "b", "c"])

# 右操作(和list完全一致)
d.append("d")
d.pop()  # 返回并删除'd'

# 左操作(list没有的高效方法)
d.appendleft("z")
d.popleft()  # 返回并删除'z'

print(d)  # 输出: deque(['a', 'b', 'c'])

核心特点

  1. 两端O(1)操作:替代list做队列/栈首选
  2. 线程安全:多个线程可以同时安全地操作
  3. 支持最大长度限制:超出后会自动把最早加的元素挤出去

进阶小技巧

# 1. 限制最大长度(适合做滑动窗口)
window = deque(maxlen=3)
window.extend([1, 2, 3])
window.append(4)  # 自动挤掉最早的1
print(window)  # 输出: deque([2, 3, 4], maxlen=3)

# 2. 旋转操作
nums = deque([1, 2, 3, 4, 5])
nums.rotate(1)  # 向右旋转1位:最后一个元素移到最前面
print(nums)  # 输出: deque([5, 1, 2, 3, 4])
nums.rotate(-2)  # 向左旋转2位:前两个元素移到最后面
print(nums)  # 输出: deque([2, 3, 4, 5, 1])

3. defaultdict:带默认值的字典

用普通dict时,最烦的就是访问不存在的键会报KeyError——比如统计单词频率,每次都要写if key in dict: dict[key] +=1 else: dict[key] =1

defaultdict完美解决这个问题:它是dict的子类,初始化时需要传入一个默认值工厂函数,访问不存在的键时,会自动用工厂函数生成默认值并填入字典。

基本用法

from collections import defaultdict

# 场景1:统计频率(工厂函数用int,默认返回0)
freq = defaultdict(int)
for char in "hello world":
    freq[char] += 1
print(freq)  # 输出: defaultdict(<class 'int'>, {'h': 1, 'e': 1, ..., 'd': 1})

# 场景2:分组(工厂函数用list,默认返回空列表)
groups = defaultdict(list)
words = ["apple", "banana", "apricot", "blueberry"]
for word in words:
    groups[word[0]].append(word)
print(groups)  # 输出: defaultdict(<class 'list'>, {'a': ['apple', 'apricot'], ...})

核心特点

  1. 避免KeyError:不用写冗余的判断逻辑
  2. 完全兼容dict:可以用所有dict的方法,比如keys()values()items()
  3. 工厂函数灵活:可以用任何无参可调用对象(内置的int/list/set,或者自己写的函数)

进阶小技巧

# 自定义工厂函数(生成嵌套字典)
def nested_dict_factory():
    return defaultdict(int)

# 二维计数器:统计每个班级的男女生人数
class_gender = defaultdict(nested_dict_factory)
class_gender["一班"]["男"] += 1
class_gender["一班"]["女"] += 2
class_gender["二班"]["男"] += 3
print(class_gender["一班"]["男"])  # 输出: 1
print(class_gender["三班"]["女"])  # 输出: 0(自动生成)

4. Counter:专门做计数的字典

统计频率是defaultdict(int)的一个常见场景,但Counter把这个场景做到了极致——它内置了most_common()、数学运算、多重集操作等专属功能,代码更简洁!

基本用法

from collections import Counter

# 从可迭代对象初始化(字符串、列表、元组都可以)
c1 = Counter("gallahad")  # 字符串
c2 = Counter([1, 2, 2, 3, 3, 3])  # 列表
c3 = Counter({"a": 3, "b": 1})  # 甚至可以直接传字典

print(c1)  # 输出: Counter({'a': 3, 'l': 2, 'g': 1, ...})
print(c3["c"])  # 访问不存在的键返回0,不会报错

核心专属功能

# 1. 获取前n个最常见的元素(most_common())
print(c2.most_common(2))  # 输出: [(3, 3), (2, 2)]

# 2. 多重集数学运算(+、-、&、|)
c4 = Counter(a=3, b=2, c=1)
c5 = Counter(a=1, b=3, d=1)

print(c4 + c5)  # 加法:所有键的计数相加(只保留正数)
print(c4 - c5)  # 减法:只保留相减后正数的键
print(c4 & c5)  # 交集:取两个字典中相同键的最小值
print(c4 | c5)  # 并集:取两个字典中相同键的最大值

5. OrderedDict(3.7+更新说明)

Python 3.7开始,普通dict已经官方保证保持键的插入顺序了,那OrderedDict还有用吗?

有用!它主要有两个普通dict没有的功能:

  1. 特定顺序操作:比如move_to_end()把键移到开头/结尾
  2. 顺序敏感的相等性:两个OrderedDict只有键顺序和值都相同时才相等

常用专属功能

from collections import OrderedDict

od = OrderedDict.fromkeys("abcde")  # 初始化:键是a、b、c、d、e,值都是None

# 1. move_to_end()
od.move_to_end("b")  # 把b移到最后
print(list(od.keys()))  # 输出: ['a', 'c', 'd', 'e', 'b']
od.move_to_end("d", last=False)  # last=False移到最前面
print(list(od.keys()))  # 输出: ['d', 'a', 'c', 'e', 'b']

# 2. 顺序敏感的相等性
d_normal1 = {"a":1, "b":2}
d_normal2 = {"b":2, "a":1}
print(d_normal1 == d_normal2)  # 普通dict:True

od1 = OrderedDict([("a",1), ("b",2)])
od2 = OrderedDict([("b",2), ("a",1)])
print(od1 == od2)  # OrderedDict:False

6. 其他实用工具类

UserDict/UserList/UserString

这些是容器的包装器类,不是直接继承内置容器,但用法几乎完全一致。为什么要用它们?因为直接继承内置容器(比如dict)有时候会有一些隐藏的坑(比如某些内置方法不会调用我们重写的__setitem__),而继承UserDict/UserList/UserString就不会有这个问题。

举个自定义“缺失键返回提示”的字典例子:

from collections import UserDict

class HintDict(UserDict):
    def __missing__(self, key):
        return f"💡 提示:找不到键 '{key}'"

hd = HintDict({"name": "小明"})
print(hd["name"])  # 输出: 小明
print(hd["age"])  # 输出: 💡 提示:找不到键 'age'

总结

collections模块的核心集合类可以用一张表快速记住:

类名核心功能典型场景
namedtuple带属性名的不可变元组轻量不可变对象(坐标、配置项)
deque两端O(1)操作的双端队列队列、栈、滑动窗口
defaultdict带默认值的字典统计频率、分组、嵌套字典
Counter专门做计数的字典词频统计、多重集操作
OrderedDict支持顺序操作的字典LRU缓存、顺序敏感的配置
ChainMap组合多个字典的视图(省内存)配置优先级管理(环境变量>用户配置>默认配置)
UserX安全自定义容器的包装器开发自定义字典/列表/字符串类

这些工具类能显著提高代码的可读性性能,以后遇到特定场景,先别急着自己造轮子,先看看collections里有没有现成的!