← 返回Python进阶编程目录
← 返回学习笔记首页
专题: Python进阶编程系统学习
关键词: Python, collections, deque, Counter, defaultdict, OrderedDict, ChainMap
一、collections模块概述
Python内置的collections模块提供了比基础数据类型(list、dict、set、tuple)更强大的容器数据结构,在特定场景下能显著提升代码性能与可读性。该模块扩展了Python的核心容器功能,为开发者提供了处理复杂数据结构的"瑞士军刀"。
collections模块共包含九个容器数据类型,其中最常用的有deque(双端队列)、Counter(计数器)、defaultdict(默认值字典)、OrderedDict(有序字典)和ChainMap(链式映射)。此外,UserDict、UserList和UserString三个基类方便开发者通过继承创建自定义容器。
理解这些容器的适用场景和底层实现,是Python进阶路上不可或缺的一环。它们不仅能大幅简化代码逻辑,还能在特定操作上将时间复杂度从O(n)降低到O(1)。
二、deque双端队列
deque(double-ended queue,双端队列)是collections模块中最常用的容器之一。与列表不同,deque在两端进行append和pop操作的时间复杂度均为O(1),而列表在头部进行insert和pop操作的时间复杂度是O(n)。
2.1 基本操作
deque提供了与列表相似的接口,但额外支持左端的操作。创建deque时可以传入任意可迭代对象作为初始数据。当需要频繁在序列两端进行增删操作时,deque是列表的理想替代品。
from collections import deque
# 创建deque
dq = deque([1, 2, 3])
print(dq) # deque([1, 2, 3])
# 两端添加元素
dq.append(4) # 右端添加 -> deque([1, 2, 3, 4])
dq.appendleft(0) # 左端添加 -> deque([0, 1, 2, 3, 4])
# 两端弹出元素
right = dq.pop() # 右端弹出 -> 4, deque变为[0, 1, 2, 3]
left = dq.popleft() # 左端弹出 -> 0, deque变为[1, 2, 3]
# 扩展
dq.extend([4, 5]) # 右端扩展 -> deque([1, 2, 3, 4, 5])
dq.extendleft([-1, 0]) # 左端扩展(注意逆序插入) -> deque([0, -1, 1, 2, 3, 4, 5])
2.2 maxlen限制长度
maxlen参数是deque的一个独特特性。当设置最大长度后,deque会自动管理元素数量:添加新元素时,如果已达上限,另一端的元素会自动被挤出。这一特性非常适合实现滑动窗口、历史记录缓冲区和日志尾随等场景。
from collections import deque
# 固定长度的deque —— 自动淘汰旧元素
history = deque(maxlen=5)
for i in range(10):
history.append(i)
print(f"添加 {i}: {list(history)}")
# 输出:
# 添加 0: [0]
# 添加 1: [0, 1]
# ...
# 添加 5: [1, 2, 3, 4, 5] ← 0被挤出
# 添加 9: [5, 6, 7, 8, 9] ← 始终只保留5个
# 实用案例:保留文件最后N行
def tail(filename, n=10):
"""读取文件的最后n行"""
with open(filename, 'r', encoding='utf-8') as f:
return deque(f, maxlen=n)
2.3 rotate循环移位
rotate方法提供了一种高效的循环移位操作,类似于列表切片轮转,但无需创建新列表。正数参数表示向右旋转(元素从右端移到左端),负数参数表示向左旋转。该操作的时间复杂度为O(k),其中k为旋转步数。
from collections import deque
dq = deque(range(1, 7)) # deque([1, 2, 3, 4, 5, 6])
# 向右旋转2步
dq.rotate(2)
print(list(dq)) # [5, 6, 1, 2, 3, 4]
# 向左旋转3步
dq.rotate(-3)
print(list(dq)) # [2, 3, 4, 5, 6, 1]
# 实用案例:轮播图数据源
class Carousel:
def __init__(self, items):
self._items = deque(items)
def next(self):
self._items.rotate(-1)
return self._items[0]
def prev(self):
self._items.rotate(1)
return self._items[0]
carousel = Carousel(['A', 'B', 'C', 'D'])
print(carousel.next()) # B
print(carousel.next()) # C
print(carousel.prev()) # B
2.4 deque vs list 性能对比
选择deque还是list取决于具体的操作模式。以下表格总结了关键操作的性能差异:
操作 list deque
末尾append/pop O(1) O(1)
头部insert/pop O(n) O(1)
中间insert/delete O(n) O(n)
随机访问 arr[i] O(1) O(n)
内存占用 较低 较高(双链表开销)
实践建议: 如果代码中大量使用list[0]插入或list.pop(0),请果断改用deque。但如果需要频繁的随机索引访问(如arr[500]),list仍是最佳选择。deque的内部实现是双向链表加连续内存块的混合结构,因此随机访问需要遍历。
三、Counter计数器
Counter是dict的子类,专门用于统计可哈希对象的出现次数。它的设计初衷是让"计数"这一常见需求的代码更简洁优雅。Counter的键是待统计元素,值是出现次数(整数)。访问不存在的键时返回0而非抛出KeyError。
3.1 创建与基本操作
Counter可以通过多种方式创建:传入可迭代对象、字典、或使用关键字参数。无论哪种方式,Counter底层都自动完成了计数逻辑。
from collections import Counter
# 方式1:传入可迭代对象
word_counts = Counter("mississippi")
print(word_counts)
# Counter({'i': 4, 's': 4, 'p': 2, 'm': 1})
# 方式2:传入字典
fruit_counts = Counter({'apple': 3, 'banana': 2, 'orange': 1})
# 方式3:使用关键字参数
color_counts = Counter(red=5, blue=3, green=2)
# 访问计数(不存在返回0,不抛异常)
print(word_counts['z']) # 0
# 获取所有元素(按计数重复展开)
print(list(word_counts.elements()))
# ['m', 'i', 'i', 'i', 'i', 's', 's', 's', 's', 'p', 'p']
# 获取最常见的N个元素
print(word_counts.most_common(2))
# [('i', 4), ('s', 4)]
3.2 进阶方法:subtract 与 update
update方法用于增加计数(合并),而subtract用于减少计数(扣除)。两者的参数都可以是任何可迭代对象或Counter实例。与直接相减不同,subtract允许结果为负数,这在库存扣减、资源调度等场景中非常有用。
from collections import Counter
inventory = Counter(apple=10, banana=5, orange=3)
# update: 增加计数
new_shipment = Counter(apple=20, banana=10, kiwi=5)
inventory.update(new_shipment)
print(inventory)
# Counter({'apple': 30, 'banana': 15, 'kiwi': 5, 'orange': 3})
# subtract: 减少计数(允许负数)
sold = Counter(apple=25, banana=16, kiwi=3)
inventory.subtract(sold)
print(inventory)
# Counter({'apple': 5, 'orange': 3, 'kiwi': 2, 'banana': -1})
# 注意:banana变成负数,表示超卖
# 移除计数为0或负数的元素
inventory = +inventory # +操作符保留正数
print(inventory)
# Counter({'apple': 5, 'orange': 3, 'kiwi': 2})
3.3 算术运算 + - & |
Counter支持全套算术运算符。加法和减法分别对应元素的并集和差集(计数相加减),交运算(&)取计数最小值,并运算(|)取计数最大值。这些运算符返回新的Counter对象,不会修改原对象。特别值得注意的是,减法运算符会去除计数为0或负数的结果。
from collections import Counter
c1 = Counter(a=3, b=2, c=1, d=0)
c2 = Counter(a=1, b=2, c=3, e=1)
# 加法:计数相加
print(c1 + c2)
# Counter({'a': 4, 'b': 4, 'c': 4, 'e': 1})
# 减法:计数相减(去除<=0的元素)
print(c1 - c2)
# Counter({'a': 2}) # b: 2-2=0被移除, c: 1-3=-2被移除
# 交运算:取最小计数
print(c1 & c2)
# Counter({'a': 1, 'b': 2, 'c': 1})
# 并运算:取最大计数
print(c1 | c2)
# Counter({'a': 3, 'c': 3, 'b': 2, 'e': 1})
# 实用案例:文本相似度分析
def jaccard_similarity(text1, text2):
"""基于词频的Jaccard相似度"""
words1 = Counter(text1.lower().split())
words2 = Counter(text2.lower().split())
intersection = sum((words1 & words2).values())
union = sum((words1 | words2).values())
return intersection / union if union > 0 else 0.0
s1 = "python is great and python is powerful"
s2 = "python is great and java is powerful"
print(f"相似度: {jaccard_similarity(s1, s2):.2%}") # 相似度: 75.00%
Counter使用要点: Counter默认返回0而不是抛出KeyError,这使得计数累加代码更简洁。配合most_common方法可以轻松实现"top-N查询"。算术运算符为多数据源聚合提供了声明式编程体验。
四、defaultdict默认值字典
defaultdict是dict的子类,它最大的特点是当访问不存在的键时,会自动调用预先提供的工厂函数生成默认值并存入字典。这一特性让"分组"和"计数"类代码变得极其简洁,不再需要手动检查键是否存在。
4.1 list/set/int工厂函数
不同的工厂函数适用于不同的场景。list工厂用于构建值列表的分组结构,set工厂用于去重分组,int工厂用于计数(初始值为0)。选择正确的工厂函数能让代码语义更加清晰。
from collections import defaultdict
# === int工厂:计数 ===
# 传统写法
word_counts = {}
for word in "apple banana apple orange banana apple".split():
if word not in word_counts:
word_counts[word] = 0
word_counts[word] += 1
# defaultdict写法
counts = defaultdict(int)
for word in "apple banana apple orange banana apple".split():
counts[word] += 1
print(dict(counts))
# {'apple': 3, 'banana': 2, 'orange': 1}
# === list工厂:分组 ===
# 将文件名按扩展名分组
files = ["a.py", "b.py", "c.txt", "d.jpg", "e.txt"]
groups = defaultdict(list)
for f in files:
ext = f.split('.')[1]
groups[ext].append(f)
print(dict(groups))
# {'py': ['a.py', 'b.py'], 'txt': ['c.txt', 'e.txt'], 'jpg': ['d.jpg']}
# === set工厂:去重分组 ===
students = [("A班", "张三"), ("A班", "李四"), ("A班", "张三"),
("B班", "王五"), ("B班", "李四")]
class_members = defaultdict(set)
for cls, name in students:
class_members[cls].add(name)
print({k: list(v) for k, v in class_members.items()})
# {'A班': ['张三', '李四'], 'B班': ['王五', '李四']}
4.2 嵌套字典
使用defaultdict可以轻松创建多层嵌套字典结构,常见于树形数据、多维统计等场景。核心技巧是将工厂函数设为另一个defaultdict的构造函数,并通过lambda闭包实现任意深度的嵌套。但需要注意,这种做法在访问不存在的深层键时会自动创建整个路径,可能引入意外的空结构。
from collections import defaultdict
# 两层嵌套:defaultdict(lambda: defaultdict(int))
visit_stats = defaultdict(lambda: defaultdict(int))
# 记录用户每日访问量
visit_stats["user_001"]["2026-05-01"] += 1
visit_stats["user_001"]["2026-05-02"] += 3
visit_stats["user_002"]["2026-05-01"] += 2
print(visit_stats["user_001"]["2026-05-01"]) # 1
print(dict(visit_stats))
# {'user_001': defaultdict(, {'2026-05-01': 1, '2026-05-02': 3}),
# 'user_002': defaultdict(, {'2026-05-01': 2})}
# 三层嵌套:多维度统计
sales = defaultdict(lambda: defaultdict(lambda: defaultdict(int)))
# 记录 区域 > 产品 > 月份 的销售额
sales["华东"]["iPhone"]["1月"] += 120000
sales["华东"]["iPhone"]["2月"] += 95000
sales["华北"]["iPad"]["1月"] += 68000
print(sales["华东"]["iPhone"]["1月"]) # 120000
# 使用 partial 创建嵌套defaultdict
from functools import partial
def nested_defaultdict():
return defaultdict(nested_defaultdict)
# 等价于递归defaultdict
tree = nested_defaultdict()
tree["level1"]["level2"]["level3"] = "deep value"
print(tree["level1"]["level2"]["level3"]) # deep value
注意事项: 嵌套defaultdict在访问不存在的键时,会自动创建中间层级的defaultdict实例。这意味着单纯读取不存在的路径也会产生副作用(创建空字典)。如果不想在读取时自动创建,应在访问前使用in运算符检查键是否存在,或改用普通的dict配合setdefault方法。
4.3 defaultdict vs dict.setdefault
dict.setdefault方法与defaultdict功能类似,但使用方式不同。setdefault每次调用都会创建默认值对象(即使键已存在),而defaultdict只在键不存在时才调用工厂函数创建一次。在性能敏感场景下,defaultdict通常更高效。
from collections import defaultdict
data = [("fruit", "apple"), ("fruit", "banana"), ("drink", "cola")]
# 使用 dict.setdefault
result1 = {}
for k, v in data:
result1.setdefault(k, []).append(v)
print(result1) # {'fruit': ['apple', 'banana'], 'drink': ['cola']}
# 使用 defaultdict
result2 = defaultdict(list)
for k, v in data:
result2[k].append(v)
print(dict(result2)) # {'fruit': ['apple', 'banana'], 'drink': ['cola']}
# defaultdict的优势:代码更简洁,且不会为已存在的键创建临时空列表
五、OrderedDict有序字典
OrderedDict是dict的子类,它记住了键值对的插入顺序。在Python 3.7+中,普通dict已经能保持插入顺序,但OrderedDict仍然提供了普通dict没有的两个重要方法:move_to_end和增强版popitem,使其在某些需要显式控制元素顺序的场景中不可替代。
5.1 核心特性与move_to_end
OrderedDict的最强特性是move_to_end方法,它可以将指定键移动到字典的末尾(或开头)。这一特性非常适合实现LRU缓存淘汰算法:每次访问某个键时,调用move_to_end将其标记为最近使用。
from collections import OrderedDict
od = OrderedDict()
od['a'] = 1
od['b'] = 2
od['c'] = 3
print(list(od.keys())) # ['a', 'b', 'c']
# move_to_end: 将键移到末尾
od.move_to_end('a')
print(list(od.keys())) # ['b', 'c', 'a']
# move_to_end(last=False): 将键移到开头
od.move_to_end('c', last=False)
print(list(od.keys())) # ['c', 'b', 'a']
# popitem(last=True): 移除并返回最后一个键值对(LIFO)
last = od.popitem(last=True)
print(last) # ('a', 1)
# popitem(last=False): 移除并返回第一个键值对(FIFO)
first = od.popitem(last=False)
print(first) # ('c', 1)
5.2 实战:实现LRU缓存
LRU(Least Recently Used,最近最少使用)缓存是OrderedDict最具代表性的应用场景。结合move_to_end和popitem方法,可以用不到20行代码实现一个高效的LRU缓存类。
from collections import OrderedDict
class LRUCache:
"""使用OrderedDict实现的LRU缓存"""
def __init__(self, capacity):
self.capacity = capacity
self.cache = OrderedDict()
def get(self, key):
if key not in self.cache:
return -1
# 将访问的元素移到末尾(标记为最近使用)
self.cache.move_to_end(key)
return self.cache[key]
def put(self, key, value):
if key in self.cache:
# 键已存在,移到末尾并更新值
self.cache.move_to_end(key)
self.cache[key] = value
if len(self.cache) > self.capacity:
# 移除第一个键值对(最近最少使用)
self.cache.popitem(last=False)
def __repr__(self):
return f"LRUCache({dict(self.cache)})"
# 测试
lru = LRUCache(3)
lru.put('A', 1)
lru.put('B', 2)
lru.put('C', 3)
print(lru) # LRUCache({'A': 1, 'B': 2, 'C': 3})
lru.get('A') # 访问A,A变为最近使用
lru.put('D', 4) # 超出容量,淘汰最近最少使用的B
print(lru) # LRUCache({'C': 3, 'A': 1, 'D': 4})
5.3 OrderedDict vs dict
在Python 3.7+中,普通dict也会保持插入顺序,因此在需要顺序保持的简单场景下可以通用。但是,以下场景仍然必须使用OrderedDict:需要调用move_to_end显式调整顺序、需要popitem的last参数控制LIFO/FIFO、以及需要顺序敏感的相等性比较(两个普通dict即使元素顺序不同也被认为是相等的)。
from collections import OrderedDict
# 顺序敏感比较
od1 = OrderedDict([('a', 1), ('b', 2)])
od2 = OrderedDict([('b', 2), ('a', 1)])
print(od1 == od2) # False —— OrderedDict比较时考虑顺序
d1 = {'a': 1, 'b': 2}
d2 = {'b': 2, 'a': 1}
print(d1 == d2) # True —— 普通dict不考虑顺序
六、ChainMap链式映射
ChainMap将多个映射(字典)组合成一个逻辑上的单一视图,在查找键时按顺序搜索各个映射,返回第一个匹配到的值。它不会合并底层字典,而是维护一个映射列表,所有操作都在这个列表上进行。这种设计在管理作用域链、配置覆盖和命名空间等场景中极为有用。
6.1 基本用法与搜索顺序
ChainMap接受任意数量的映射作为参数,搜索顺序从左到右(先传入的先搜索)。当多个映射中存在相同的键时,最左侧的映射优先。这种"优先覆盖"的行为正是变量作用域链的本质。
from collections import ChainMap
# 模拟配置覆盖:命令行参数 > 环境变量 > 默认配置
defaults = {'host': 'localhost', 'port': 8080, 'debug': False}
env_config = {'host': 'example.com', 'port': 443}
cmd_args = {'host': 'api.example.com'}
config = ChainMap(cmd_args, env_config, defaults)
print(config['host']) # api.example.com (来自cmd_args)
print(config['port']) # 443 (来自env_config)
print(config['debug']) # False (来自defaults)
# 转换为普通字典
print(dict(config))
# {'host': 'api.example.com', 'port': 443, 'debug': False}
# 注意:修改操作只影响第一个映射
config['timeout'] = 30 # 写入cmd_args
config['host'] = 'new.example.com' # 修改cmd_args中的host
print(cmd_args)
# {'host': 'new.example.com', 'timeout': 30}
print(env_config) # 不受影响
# {'host': 'example.com', 'port': 443}
6.2 new_child 与 parents
new_child方法在当前ChainMap前面插入一个新的映射,用于创建新的作用域层次。parents属性则返回去掉最顶层映射后的新ChainMap,相当于退出一层作用域。这种操作模式完美契合编程语言的作用域管理。
from collections import ChainMap
# 模拟函数调用作用域链
global_scope = {'x': 1, 'y': 2, 'z': 3}
scope = ChainMap(global_scope)
def outer_function():
local_scope = {'x': 10, 'w': 4}
# outer作用域 = local_scope + global_scope
outer_scope = scope.new_child(local_scope)
def inner_function():
inner_scope = {'y': 20}
# inner作用域 = inner_scope + local_scope + global_scope
current = outer_scope.new_child(inner_scope)
print(current['x']) # 10 (来自inner_scope? 不, 来自local_scope)
# 实际结果: 10 (来自inner_scope没有x, 所以找local_scope中的x=10)
print(current['y']) # 20 (来自inner_scope)
print(current['z']) # 3 (来自global_scope)
print(current['w']) # 4 (来自local_scope)
# parents: 退出inner作用域
outer_scope = current.parents
print(outer_scope['y']) # 2 (回到global_scope的y)
inner_function()
outer_function()
6.3 maps 属性与动态更新
ChainMap的maps属性返回一个映射列表,可以直接操作这个列表来增删作用域层次。由于ChainMap持有底层映射的引用,任何对原始字典的修改都会立即反映到ChainMap的查找结果中。
from collections import ChainMap
user_settings = {'theme': 'dark', 'lang': 'zh'}
system_settings = {'theme': 'light', 'timeout': 60}
cm = ChainMap(user_settings, system_settings)
# 直接操作maps列表
print(cm.maps)
# [{'theme': 'dark', 'lang': 'zh'}, {'theme': 'light', 'timeout': 60}]
# 动态添加一个新的映射层
cm.maps.insert(0, {'lang': 'en', 'debug': True})
print(cm['lang']) # en (来自新插入的映射)
# 动态删除映射层
cm.maps.pop(0)
print(cm['lang']) # zh (回到user_settings)
# 底层字典的修改实时可见
user_settings['theme'] = 'light'
print(cm['theme']) # light (实时反映修改)
# 实用案例:HTTP请求头管理
default_headers = ChainMap({
'User-Agent': 'Python/3.12',
'Accept': 'application/json',
})
# 每次请求可以创建新的子ChainMap添加特定头部
request_headers = default_headers.new_child({
'Authorization': 'Bearer token123',
'X-Request-ID': 'abc-123',
})
print(request_headers['Authorization']) # Bearer token123
print(request_headers['User-Agent']) # Python/3.12
ChainMap vs dict.update: ChainMap不会复制底层字典,创建开销是O(1),且修改原始字典会实时反映。相比之下,dict.update会创建新字典(O(n)开销),且修改原始字典不会影响合并结果。当需要频繁创建多个配置层或作用域链时,ChainMap的内存效率和灵活性明显优于update。
七、UserDict / UserList / UserString 自定义容器
UserDict、UserList和UserString是collections模块中提供的三个包装类,它们分别作为dict、list和str的子类替代品。直接继承dict/list/str来创建自定义容器时,由于CPython的底层C实现,某些方法的重写行为可能不符合预期。而UserDict等包装类将所有操作委托给内部的data对象,使得子类化行为更加可靠和可预测。
7.1 UserDict 自定义字典
from collections import UserDict
class CaseInsensitiveDict(UserDict):
"""大小写不敏感的字典"""
def __setitem__(self, key, value):
super().__setitem__(key.lower(), value)
def __getitem__(self, key):
return super().__getitem__(key.lower())
def __contains__(self, key):
return super().__contains__(key.lower())
def __delitem__(self, key):
super().__delitem__(key.lower())
# 使用
cid = CaseInsensitiveDict()
cid['Name'] = 'Alice'
cid['EMAIL'] = 'alice@example.com'
print(cid['name']) # Alice
print(cid['Email']) # alice@example.com
print('NAME' in cid) # True
print(dict(cid))
# {'name': 'Alice', 'email': 'alice@example.com'}
class DefaultListDict(UserDict):
"""值自动包装为列表的字典"""
def __setitem__(self, key, value):
if not isinstance(value, list):
value = [value]
super().__setitem__(key, value)
def add(self, key, item):
if key not in self.data:
self.data[key] = []
self.data[key].append(item)
dld = DefaultListDict()
dld['tags'] = 'python' # 自动包装为列表
dld.add('tags', 'collections')
print(dld['tags']) # ['python', 'collections']
7.2 UserList 与 UserString
from collections import UserList, UserString
class LimitedList(UserList):
"""限制最大长度的列表"""
def __init__(self, maxlen, initlist=None):
self.maxlen = maxlen
super().__init__(initlist if initlist else [])
def append(self, item):
if len(self.data) >= self.maxlen:
raise ValueError(f"列表已达最大长度 {self.maxlen}")
super().append(item)
def insert(self, i, item):
if len(self.data) >= self.maxlen:
raise ValueError(f"列表已达最大长度 {self.maxlen}")
super().insert(i, item)
ll = LimitedList(3, [1, 2])
ll.append(3)
try:
ll.append(4) # 抛出 ValueError
except ValueError as e:
print(e) # 列表已达最大长度 3
class TrimmedString(UserString):
"""自动去除两端空格的字符串"""
def __init__(self, seq):
if isinstance(seq, str):
seq = seq.strip()
super().__init__(seq)
ts = TrimmedString(" hello world ")
print(repr(ts.data)) # 'hello world'
print(ts.upper()) # HELLO WORLD (继承str的所有方法)
UserDict vs 直接继承dict: 继承dict时,如果重写了__getitem__,dict的get方法可能不会自动使用重写后的__getitem__。而UserDict通过将所有操作委托给内部self.data字典,确保了重写方法的覆盖完整性。同理,UserList和UserString也遵循相同的设计原则。
八、性能对比与实践建议
8.1 容器选择决策树
在实际开发中,选择合适的容器类型可以遵循以下决策逻辑:
需要双端高效增删? → deque
需要统计元素出现次数? → Counter
需要自动为不存在的键提供默认值? → defaultdict
需要显式控制键值对顺序(move_to_end)? → OrderedDict
需要合并多个字典且避免复制开销? → ChainMap
需要继承dict/list/str自定义行为? → UserDict/UserList/UserString
8.2 综合案例:简易文本分析器
以下综合案例展示了如何将collections模块的多个容器组合使用,构建一个简易但功能完整的文本分析工具。通过这个案例可以体会各容器之间的协同效应。
from collections import deque, Counter, defaultdict, OrderedDict, ChainMap
class TextAnalyzer:
"""综合使用collections模块的文本分析工具"""
def __init__(self, text, max_history=5):
self.text = text
self.words = text.lower().split()
# Counter:词频统计
self.word_freq = Counter(self.words)
# deque:分析历史记录
self.history = deque(maxlen=max_history)
# defaultdict:按首字母分组
self.letter_groups = defaultdict(list)
def top_words(self, n=5):
"""获取最常见的n个词"""
result = self.word_freq.most_common(n)
self.history.append(f"top_words(n={n})")
return result
def group_by_first_letter(self):
"""按首字母对单词分组"""
for word in set(self.words):
letter = word[0] if word else '?'
self.letter_groups[letter].append(word)
self.history.append("group_by_first_letter()")
return dict(self.letter_groups)
def word_intersection(self, other_text):
"""计算两个文本的共同词汇"""
other_freq = Counter(other_text.lower().split())
common = self.word_freq & other_freq
self.history.append("word_intersection()")
return list(common.keys())
def search(self, keyword):
"""搜索关键词出现的位置(使用OrderedDict保留顺序)"""
positions = OrderedDict()
for idx, word in enumerate(self.words):
if keyword in word:
positions[idx] = word
self.history.append(f"search(keyword={keyword})")
return positions
def get_scope(self):
"""返回当前分析范围(使用ChainMap组合多层面信息)"""
stats = {
'total_words': len(self.words),
'unique_words': len(self.word_freq),
}
config = {
'max_history': self.history.maxlen,
}
return ChainMap(stats, config)
def summary(self):
"""生成分析摘要"""
scope = self.get_scope()
return (
f"总词数: {scope['total_words']}, "
f"不重复词数: {scope['unique_words']}, "
f"历史上限: {scope['max_history']}条, "
f"最近操作: {list(self.history)}"
)
# 使用示例
text = ("Python is great. Python is powerful. "
"Python is easy to learn. collections module is useful.")
analyzer = TextAnalyzer(text)
print("=== Top词汇 ===")
print(analyzer.top_words(3))
print("\n=== 首字母分组 ===")
groups = analyzer.group_by_first_letter()
for letter in sorted(groups):
print(f" {letter}: {groups[letter]}")
print("\n=== 分析摘要 ===")
print(analyzer.summary())
8.3 总结
collections模块是Python标准库中最实用、最常用的模块之一。它提供的容器类型填补了内置数据类型在特定场景下的空白,既能提升代码性能,又能增强代码可读性。掌握这些容器的适用场景和底层实现原理,是区分Python初学者和进阶者的重要标志。
核心要点回顾:
deque — 双端O(1)增删,maxlen自动淘汰,rotate循环移位
Counter — 计数与Top-N查询,算术运算(+-&|)用于多数据源聚合
defaultdict — 自动默认值消除键检查样板代码,嵌套字典利器
OrderedDict — move_to_end实现顺序调整,LRU缓存核心组件
ChainMap — 零拷贝多字典合并,作用域链与配置覆盖首选
UserDict/UserList/UserString — 可靠的自定义容器基类