1. 为什么需要collections模块

Python内置了list、tuple、dict、set等基础数据结构,它们能解决大部分问题。但在实际开发中,我们经常会遇到一些特殊场景:

  • 需要给字典设置默认值避免KeyError
  • 想要统计元素出现频率
  • 需要记住字典键的插入顺序
  • 希望元组的元素可以通过属性访问
  • 需要高效的双端队列操作

这些场景如果用基础数据结构实现,代码会变得冗长且低效。collections模块就是为了解决这些问题而生的,它提供了5个特别有用的数据结构工具类:

from collections import namedtuple, Counter, defaultdict, OrderedDict, deque

我刚开始学Python时也好奇,为什么这些工具不直接内置到基础类型中?后来在真实项目中踩过几次坑才明白:保持核心语言的简洁性很重要,而collections模块就像是一个"瑞士军刀"扩展包,需要时随时取用。

2. namedtuple:带字段名的元组

2.1 基本用法

假设我们要处理二维坐标点,用普通元组表示:

p = (1, 2)

一个月后看这段代码,你能记得 p[0] 是x坐标还是y坐标吗?

namedtuple完美解决了这个问题:

from collections import namedtuple

# 创建一个Point类
Point = namedtuple('Point', ['x', 'y'])
p = Point(1, 2)

print(p.x)  # 1
print(p.y)  # 2

我特别喜欢用namedtuple替代简单的类定义,比如配置项、数据库记录等。它比普通类更节省内存(因为继承自tuple),同时保持了代码可读性。

2.2 高级技巧

  1. 类型提示支持
from typing import NamedTuple

class Point(NamedTuple):
    x: float
    y: float
  1. 默认参数 (Python 3.7+):
Point = namedtuple('Point', ['x', 'y'], defaults=[0, 0])
p = Point()  # Point(x=0, y=0)
  1. 转换字典
d = {'x': 3, 'y': 4}
p = Point(**d)

实际案例:我在处理GPS轨迹数据时,用namedtuple表示坐标点,代码既简洁又易读:

TrackPoint = namedtuple('TrackPoint', ['latitude', 'longitude', 'timestamp'])
points = [TrackPoint(39.9, 116.4, '2023-01-01 08:00')]

3. Counter:专业的计数器

3.1 统计元素频率

统计单词出现次数,不用Counter的写法:

words = ['apple', 'banana', 'apple', 'orange']
count = {}
for word in words:
    if word not in count:
        count[word] = 0
    count[word] += 1

用Counter只需一行:

from collections import Counter

count = Counter(words)
# Counter({'apple': 2, 'banana': 1, 'orange': 1})

3.2 实用方法

  1. 获取前N个常见元素
count.most_common(2)  # [('apple', 2), ('banana', 1)]
  1. 数学运算
c1 = Counter(a=3, b=1)
c2 = Counter(a=1, b=2)
c1 + c2  # Counter({'a': 4, 'b': 3})
c1 - c2  # Counter({'a': 2})

真实案例:分析Nginx日志时,我用Counter统计状态码分布:

status_counts = Counter(line.split()[8] for line in open('access.log'))
print(status_counts.most_common())

4. defaultdict:智能字典

4.1 避免KeyError

普通字典在访问不存在的键时会报错,defaultdict则返回默认值:

from collections import defaultdict

# 默认值为0
d = defaultdict(int)
print(d['a'])  # 0

# 默认值为空列表
d = defaultdict(list)
print(d['key'])  # []

4.2 分组数据

将学生按班级分组:

students = [
    ('class1', 'Alice'),
    ('class2', 'Bob'),
    ('class1', 'Charlie')
]

classes = defaultdict(list)
for class_name, student in students:
    classes[class_name].append(student)

我在处理数据库查询结果时经常用这个模式,比手动检查键是否存在要优雅得多。

5. deque:高性能双端队列

5.1 基本操作

from collections import deque

d = deque(['b', 'c'])
d.append('d')    # 右侧添加
d.appendleft('a') # 左侧添加
d.pop()          # 右侧删除
d.popleft()      # 左侧删除

5.2 实际应用

  1. 实现滑动窗口
def sliding_window(items, size):
    window = deque(maxlen=size)
    for item in items:
        window.append(item)
        if len(window) == size:
            yield list(window)
  1. 维护最近记录 (固定长度):
recent_items = deque(maxlen=10)
for new_item in data_stream:
    recent_items.append(new_item)

我在实现消息队列消费者时,deque的性能比list高出一个数量级,特别是在处理大量数据时。

6. OrderedDict:记住插入顺序的字典

6.1 保持顺序

虽然Python 3.7+的普通dict也保持插入顺序,但OrderedDict提供了额外功能:

from collections import OrderedDict

d = OrderedDict()
d['a'] = 1
d['b'] = 2
d['c'] = 3
print(list(d.keys()))  # ['a', 'b', 'c']

6.2 特殊方法

  1. 移动键到两端
d.move_to_end('a')  # 移到末尾
d.move_to_end('c', last=False)  # 移到开头
  1. 实现LRU缓存
class LRUCache:
    def __init__(self, capacity):
        self.cache = OrderedDict()
        self.capacity = capacity

    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)

我在实现API缓存层时就用到了这个模式,代码简洁且高效。

Logo

码道开发者社区,聚焦华为云码道 CodeArts 代码智能体,沉淀 Agent、Skill、鸿蒙开发实战内容,供开发者查阅资料、交流技术、分享工程实践

更多推荐