Python collections 入门+实战

文章目录

      • [第一阶段:为什么需要 collections?(背景与痛点)](#第一阶段:为什么需要 collections?(背景与痛点))
        • [1. 场景引入:内置类型的"笨重"操作](#1. 场景引入:内置类型的“笨重”操作)
        • [2. collections 的解法:更聪明的容器](#2. collections 的解法:更聪明的容器)
      • 第二阶段:核心功能全景图
      • 第三阶段:逐功能实战学习
        • [🔹 1. `Counter`:计数器的终极形态](#🔹 1. Counter:计数器的终极形态)
        • [🔹 2. `defaultdict`:告别 `KeyError` 的烦恼](#🔹 2. defaultdict:告别 KeyError 的烦恼)
        • [🔹 3. `deque`:双端队列,性能怪兽](#🔹 3. deque:双端队列,性能怪兽)
        • [🔹 4. `namedtuple`:带字段名的元组](#🔹 4. namedtuple:带字段名的元组)
        • [🔹 5. `ChainMap`:多字典合并视图](#🔹 5. ChainMap:多字典合并视图)
        • [🔹 6. `OrderedDict`:有序字典的额外能力](#🔹 6. OrderedDict:有序字典的额外能力)

第一阶段:为什么需要 collections?(背景与痛点)

1. 场景引入:内置类型的"笨重"操作

假设你要统计一篇文章中每个单词出现的次数。用最基础的内置 dict,你需要这样写:

python 复制代码
# 😩 传统做法:手动处理"键不存在"的异常
text = "apple banana apple orange banana apple"
word_count = {}

for word in text.split():
    if word not in word_count:
        word_count[word] = 0  # 每次都要判断键是否存在
    word_count[word] += 1

print(word_count)
# {'apple': 3, 'banana': 2, 'orange': 1}

或者,你可能想用 list 来记录用户的操作历史,但当你需要频繁在头部插入数据时:

python 复制代码
# 😩 传统做法:列表头部插入性能极差
history = []
history.insert(0, "login")  # O(n) 复杂度,每次都要把后面的元素往后挪
history.insert(0, "click")
history.insert(0, "logout")

🧪 动手验证 :想直观体验 list vs deque 的速度差异,见后文 deque 小节的计时代码。

痛点总结:

  • 字典初始化繁琐 :统计计数、分组数据时,总要写 if key not in dictdict.get(key, [])
  • 列表性能瓶颈list 在尾部追加很快,但在头部插入/删除极慢(因为底层是连续内存数组)。
  • 缺乏语义化容器:内置类型太通用,无法表达"这是一个固定大小的滑动窗口"或"这是一个带默认值的字典"。
2. collections 的解法:更聪明的容器

collections 模块提供了一系列针对特定场景优化的容器类,它们继承自内置类型,但增加了强大的功能:

python 复制代码
from collections import Counter

# ✅ 优雅做法:一行搞定计数
text = "apple banana apple orange banana apple"
word_count = Counter(text.split())

print(word_count)
# Counter({'apple': 3, 'banana': 2, 'orange': 1})
print(word_count.most_common(2))  # [('apple', 3), ('banana', 2)]

🧪 动手验证 1 :分别运行传统 dictCounter 版本。对比代码行数,然后尝试用 Countermost_common() 方法获取 Top 3 的单词。


第二阶段:核心功能全景图

容器类型 解决的问题 关键词
Counter 高效计数、统计频率 Counter, most_common()
defaultdict 自动初始化缺失键、消除 KeyError default_factory
deque 双端高效插入/删除、滑动窗口 appendleft(), popleft()
OrderedDict 记住键的插入顺序(Python 3.7+ 普通 dict 也有序,但 OrderedDict 有额外方法) move_to_end(), popitem()
namedtuple 轻量级不可变对象、带字段名的元组 ._fields, ._asdict()
ChainMap 合并多个字典为一个视图、配置优先级 maps, 作用域链

第三阶段:逐功能实战学习

🔹 1. Counter:计数器的终极形态

Counterdict 的子类,专门用于哈希对象计数。它不仅能统计,还提供了丰富的数学运算。

python 复制代码
from collections import Counter

# ✅ 基础计数
c = Counter("abracadabra")
print(c)  # Counter({'a': 5, 'b': 2, 'r': 2, 'c': 1, 'd': 1})

# ✅ 获取 Top N
print(c.most_common(3))  # [('a', 5), ('b', 2), ('r', 2)]

# ✅ 计数器算术运算(极其强大)
c1 = Counter(a=3, b=1)
c2 = Counter(a=1, b=2, c=3)

print(c1 + c2)  # Counter({'a': 4, 'b': 3, 'c': 3})  ← 合并计数
print(c1 - c2)  # Counter({'a': 2})                    ← 差集(只保留正数)
print(c1 & c2)  # Counter({'a': 1, 'b': 1})            ← 交集(取最小值)
print(c1 | c2)  # Counter({'a': 3, 'b': 2, 'c': 3})    ← 并集(取最大值)

⚠️ 关键规则Counter 的算术运算会自动丢弃零值和负值结果。
💡 除了 most_common(),Counter 还有几个非常常用的能力:

  • 缺失键默认返回 0c["missing"] 不会 KeyError,而是 0
  • 增量统计 update():适合分批/流式累加
  • 扣减 subtract():从总计中减去一批数据(可能出现负数计数)
  • 展开 elements():把计数"还原"为元素序列(只展开计数 > 0 的项)
python 复制代码
from collections import Counter

c = Counter("abca")
print(c["x"])  # 0(缺失键默认 0)

c.update("bbb")         # 增量统计
print(c)                # Counter({'b': 4, 'a': 2, 'c': 1})

c.subtract("abbbbb")    # 扣减(可能产生负数)
print(c["b"])           # -1(注意:subtract 不会自动丢弃负数)

print(list(c.elements()))  # 只会展开计数 > 0 的元素

🧪 动手验证 2 :用两个 Counter 分别统计两段文本的词频,然后用 - 运算找出第一段文本独有的词。


🔹 2. defaultdict:告别 KeyError 的烦恼

当你需要一个字典,且访问不存在的键时希望自动创建默认值,defaultdict 是你的救星。

python 复制代码
from collections import defaultdict

# ✅ 分组操作(最经典场景)
students = [
    ("Math", "Alice"), ("Math", "Bob"),
    ("English", "Charlie"), ("Math", "David"),
    ("English", "Eve")
]

# 😩 传统做法
groups = {}
for subject, name in students:
    if subject not in groups:
        groups[subject] = []
    groups[subject].append(name)

# ✅ defaultdict 做法
groups = defaultdict(list)  # 传入 list 作为默认工厂
for subject, name in students:
    groups[subject].append(name)  # 无需判断,键不存在时自动调用 list() 创建空列表

print(dict(groups))
# {'Math': ['Alice', 'Bob', 'David'], 'English': ['Charlie', 'Eve']}

⚠️ 访问副作用(非常重要)defaultdict 访问一个不存在的键会"顺便创建该键"。

python 复制代码
from collections import defaultdict

d = defaultdict(list)
print("Sci" in d)   # False
_ = d["Sci"]        # 读一次不存在键 → 自动创建
print("Sci" in d)   # True

如果你只是想"安全读取但不希望创建新键",用 d.get("Sci") 更合适。
💡 default_factory 的灵活性

  • defaultdict(list) → 缺失键自动创建 []
  • defaultdict(int) → 缺失键自动创建 0(等价于 Counter 的简化版)
  • defaultdict(set) → 缺失键自动创建 set()
  • defaultdict(lambda: "N/A") → 缺失键自动返回自定义默认值

🧪 动手验证 3 :创建一个 defaultdict(lambda: {"count": 0, "names": []}),用它同时记录每个类别的数量和成员名。


🔹 3. deque:双端队列,性能怪兽

deque(double-ended queue)在两端插入/删除的时间复杂度都是 O(1) ,而 list 在头部操作是 O(n)。

python 复制代码
from collections import deque

# ✅ 基础操作
d = deque([1, 2, 3])
d.appendleft(0)     # O(1) ← 头部插入
d.append(4)         # O(1) ← 尾部插入
print(d.popleft())  # 0, O(1) ← 头部弹出
print(d.pop())      # 4, O(1) ← 尾部弹出

# ✅ 滑动窗口(maxlen 参数极其有用)
window = deque(maxlen=3)
for i in range(5):
    window.append(i)
    print(list(window))
# [0]
# [0, 1]
# [0, 1, 2]
# [1, 2, 3]  ← 自动淘汰最旧元素
# [2, 3, 4]

# ✅ 旋转操作
d = deque([1, 2, 3, 4, 5])
d.rotate(2)   # 向右旋转2位
print(d)      # deque([4, 5, 1, 2, 3])
d.rotate(-1)  # 向左旋转1位
print(d)      # deque([5, 1, 2, 3, 4])

🧪 动手验证:体验 list vs deque 的速度差异

下面两段代码请分别单独运行N 越大差异越明显;如果你的机器很快,可把 N 调到 500_000 或更大)。

list:头部插入(慢)

python 复制代码
import time

N = 200_000

history = []
t0 = time.perf_counter()
for i in range(N):
    history.insert(0, i)
t1 = time.perf_counter()

print("list.insert(0, x) 用时:", round(t1 - t0, 3), "秒")

deque:头部插入(快)

python 复制代码
from collections import deque
import time

N = 200_000

history = deque()
t0 = time.perf_counter()
for i in range(N):
    history.appendleft(i)
t1 = time.perf_counter()

print("deque.appendleft(x) 用时:", round(t1 - t0, 3), "秒")

⚠️ 何时用 deque vs list

  • 只在尾部追加/弹出 → 用 list(常数因子更小)
  • 需要在头部频繁操作 / 需要固定大小滑动窗口 → 用 deque

🧪 动手验证 4 :用 deque(maxlen=5) 实现一个"最近5次搜索记录"的功能,连续添加8条记录,验证旧的记录是否被自动淘汰。


🔹 4. namedtuple:带字段名的元组

当你需要一个轻量级不可变对象,但又不想定义完整的类时,namedtuple 是完美选择。

python 复制代码
from collections import namedtuple

# ✅ 定义一个"点"类型
Point = namedtuple("Point", ["x", "y", "z"])

p = Point(1, 2, 3)

# ✅ 像元组一样索引访问
print(p[0])       # 1

# ✅ 像对象一样用字段名访问(可读性大增)
print(p.x, p.y, p.z)  # 1 2 3

# ✅ 转换为字典
print(p._asdict())  # {'x': 1, 'y': 2, 'z': 3}

# ✅ 替换字段值(返回新对象,原对象不变)
p2 = p._replace(y=10)
print(p2)  # Point(x=1, y=10, z=3)

# ✅ 不可变(安全)
# p.x = 99  # AttributeError!

💡 典型用途

  • 数据库查询结果(替代元组,增加可读性)
  • CSV 行解析
  • 函数返回多个值时的结构化封装

🧪 动手验证 5 :创建一个 User = namedtuple("User", ["name", "age", "email"]),用 _asdict() 转成字典后,用 ** 解包传给一个函数。


🔹 5. ChainMap:多字典合并视图

ChainMap 将多个字典合并为一个逻辑视图,查找时按顺序搜索,修改只影响第一个字典

python 复制代码
from collections import ChainMap

# ✅ 配置优先级(最经典场景)
defaults = {"timeout": 30, "retry": 3, "debug": False}
user_config = {"timeout": 60, "verbose": True}

config = ChainMap(user_config, defaults)

# ✅ 查找:先查 user_config,找不到再查 defaults
print(config["timeout"])  # 60(用户配置优先)
print(config["retry"])    # 3(回退到默认值)

# ✅ 修改:只影响第一个字典
config["timeout"] = 120
print(user_config)  # {'timeout': 120, 'verbose': True}
print(defaults)     # {'timeout': 30, 'retry': 3, 'debug': False} ← 未被修改

# ✅ 新增键:只添加到第一个字典
config["new_key"] = "value"
print("new_key" in user_config)  # True
print("new_key" in defaults)     # False

🔑 核心价值ChainMap视图,不是深拷贝。修改原始字典会反映在 ChainMap 中,反之亦然。这让它非常适合实现"环境变量 > 用户配置 > 默认配置"的层级覆盖。

🧪 动手验证 6 :创建三个字典 env, user, default,用 ChainMap(env, user, default) 合并,验证查找优先级和修改隔离性。


🔹 6. OrderedDict:有序字典的额外能力

Python 3.7+ 的普通 dict 已经保证插入顺序,但 OrderedDict 提供了额外的方法:

python 复制代码
from collections import OrderedDict

od = OrderedDict()
od["a"] = 1
od["b"] = 2
od["c"] = 3

# ✅ 移动到末尾
od.move_to_end("a")
print(list(od.keys()))  # ['b', 'c', 'a']

# ✅ 移动到开头
od.move_to_end("a", last=False)
print(list(od.keys()))  # ['a', 'b', 'c']

# ✅ 弹出最后一个/第一个
print(od.popitem())        # ('c', 3)
print(od.popitem(last=False))  # ('a', 1)

# ✅ 相等性比较:顺序敏感
d1 = OrderedDict(a=1, b=2)
d2 = OrderedDict(b=2, a=1)
print(d1 == d2)  # False ← 普通 dict 比较是 True

💡 何时用 OrderedDict

  • 需要 move_to_end() / popitem(last=False) 等顺序操作
  • 需要顺序敏感的相等性比较
  • 需要兼容 Python 3.6 及以下版本

🧪 动手验证 7 :用 OrderedDict 实现一个简单的 LRU 缓存:每次访问一个键就 move_to_end(),缓存满时 popitem(last=False) 淘汰最久未使用的。

相关推荐
大圣编程1 小时前
python break语句
开发语言·前端·python
luoqice1 小时前
windows下实现运行mesiamtx服务器推拉流
运维·服务器·windows
AI-好学者1 小时前
MCP企业运用全面知识点-基础篇
服务器·开发语言·网络·人工智能·python·架构
csdn_aspnet1 小时前
C# 截取或匹配字符串内包含指定字符的一些方法
c#·字符串·分割·string·匹配·截取
Sam09272 小时前
【AI 算法精讲 13】朴素贝叶斯:文本分类的基石
人工智能·python·算法·ai
海天鹰2 小时前
WIN10任务栏日期隐藏年显示星期几
windows
ai生成式引擎优化技术2 小时前
WSaiOS:面向认知资产与工程化认知流程的智能操作系统架构
python·架构·django·virtualenv·pygame
STLearner2 小时前
ICML 2026 | 时间序列(Time Series)论文总结【基础模型,生成,分类,异常检测,插补,表示学习和分析等】
论文阅读·人工智能·python·深度学习·神经网络·机器学习·数据挖掘
Rotion_深2 小时前
C# 值类型与引用类型 详解
开发语言·jvm·c#