Python collections 模块核心知识

collections 模块是 Python 的一个内置模块,它提供了标准数据类型(如 dict, list, set, tuple)的替代品,这些替代品为特定场景提供了更高性能或更方便的功能。可以把它们看作是处理数据的“瑞士军刀”。

collections 模块概览

我们将逐一深入探讨以下几个核心的数据结构:

  1. namedtuple: 带字段名的元组,增强代码可读性。
  2. deque: 高效实现两端插入和删除的双端队列。
  3. Counter: 用于计数的字典子类。
  4. defaultdict: 带有默认值的字典,处理缺失键的利器。
  5. OrderedDict: 记住键插入顺序的字典。
  6. ChainMap: 将多个字典链接成一个单一视图。
  7. 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:默认值字典

核心功能

defaultdictdict 的一个子类,它在创建时接受一个“默认工厂”(default factory)函数。当访问一个不存在的键时,它不会抛出 KeyError,而是调用这个工厂函数为该键创建一个默认值。

dict 的对比

使用普通字典处理不存在的键通常需要 try...exceptif 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* 容器包装类 方便、安全地创建自定义的字典、列表或字符串子类。
posted @ 2025-07-23 20:06  AFewMoon  阅读(20)  评论(0)    收藏  举报