【python零基础教程第13讲】Python 枚举与高级数据结构
Python 枚举与高级数据结构:告别魔法数字,拥抱优雅代码
在 Python 开发中,我们经常需要处理一组固定的常量(如状态码、星期、颜色等),以及高效地操作序列、映射和计数。早期开发者习惯用全局变量或字典来模拟,但这种方式容易导致代码脆弱、可读性差。Python 标准库提供了 enum 模块和 collections 模块,它们分别解决了“命名常量”和“高级容器”两大痛点。本文将深入剖析这些工具,并通过实际案例展示如何用它们写出更健壮、更 Pythonic 的代码。
一、enum 模块:让常量拥有身份
1.1 为什么需要枚举?
假设我们有一个订单状态系统,传统做法是定义一堆全局变量:
ORDER_PENDING = 1
ORDER_PAID = 2
ORDER_SHIPPED = 3
ORDER_COMPLETED = 4
问题显而易见:
- 变量值可以被随意修改(
ORDER_PENDING = 999) - 没有类型约束,任何整数都可以传入函数
- 打印时只能看到数字,无法直观理解含义
- 无法迭代所有状态
枚举类型 Enum 完美解决了这些问题。
1.2 基础用法:Enum 类
from enum import Enum
class OrderStatus(Enum):
PENDING = 1
PAID = 2
SHIPPED = 3
COMPLETED = 4
现在每个成员都是 OrderStatus 的实例,具有名称和值:
print(OrderStatus.PENDING) # OrderStatus.PENDING
print(OrderStatus.PENDING.name) # 'PENDING'
print(OrderStatus.PENDING.value) # 1
print(type(OrderStatus.PENDING)) # <enum 'OrderStatus'>
枚举成员是单例的,比较时使用 is 或 == 均可:
status = OrderStatus.PAID
if status is OrderStatus.PAID:
print("已支付")
1.3 枚举的三大特性
1. 唯一性与不可变性
枚举成员创建后不可修改,且名称不能重复。值可以重复(但通常不建议),此时后一个成员会成为前一个的别名。
2. 可迭代
list(OrderStatus) 返回所有成员(不含别名)。
3. 支持成员访问与查找
OrderStatus(2) # 通过值获取成员 -> OrderStatus.PAID
OrderStatus['SHIPPED'] # 通过名称获取成员 -> OrderStatus.SHIPPED
1.4 高级枚举类型
IntEnum:与整数兼容的枚举
当需要枚举值直接参与整数运算时,使用 IntEnum:
from enum import IntEnum
class Priority(IntEnum):
LOW = 1
MEDIUM = 5
HIGH = 10
print(Priority.HIGH > Priority.LOW) # True
print(Priority.HIGH + 1) # 11
StrEnum:与字符串兼容(Python 3.11+)
from enum import StrEnum
class Color(StrEnum):
RED = 'red'
GREEN = 'green'
BLUE = 'blue'
print(Color.RED.upper()) # 'RED'
Flag 与 IntFlag:位掩码枚举
适合表示可组合的权限或选项:
from enum import Flag, auto
class Permission(Flag):
READ = auto() # 1
WRITE = auto() # 2
EXECUTE = auto() # 4
# 组合权限
perm = Permission.READ | Permission.WRITE
print(perm) # Permission.READ|WRITE
print(Permission.READ in perm) # True
1.5 枚举的实用技巧
- 自动赋值:使用
auto()自动生成递增的值(从1开始) - 自定义方法:枚举类可以定义方法,实现与状态相关的逻辑
@unique装饰器:确保所有值唯一,防止意外别名
from enum import Enum, auto, unique
@unique
class Weekday(Enum):
MON = auto()
TUE = auto()
WED = auto()
THU = auto()
FRI = auto()
SAT = auto()
SUN = auto()
def is_weekend(self):
return self in (Weekday.SAT, Weekday.SUN)
二、collections 模块:超越列表与字典的高级容器
Python 内置的列表、字典、元组已经非常强大,但在特定场景下,collections 模块提供了更高效、更语义化的替代品。
2.1 deque:双端队列
适用场景:频繁在序列两端进行插入/删除操作(如队列、栈、滑动窗口)。
deque 是线程安全的,且两端操作的时间复杂度均为 O(1),而列表在头部插入是 O(n)。
from collections import deque
# 创建固定长度的队列(超出自动丢弃旧元素)
dq = deque(maxlen=3)
dq.append(1) # deque()
dq.append(2) # deque([1, 2])
dq.append(3) # deque([1, 2, 3])
dq.append(4) # deque([2, 3, 4]) # 1被挤出
# 两端操作
dq.appendleft(0) # deque([0, 2, 3, 4])
dq.pop() # 4
dq.popleft() # 0
实战:用 deque 实现最近N条记录缓存
class RecentCache:
def __init__(self, size=10):
self._queue = deque(maxlen=size)
def add(self, item):
self._queue.append(item)
def get_all(self):
return list(self._queue)
2.2 defaultdict:带默认值的字典
适用场景:需要为不存在的键自动生成默认值(如分组统计、树形结构)。
普通字典访问不存在的键会抛出 KeyError,而 defaultdict 在访问缺失键时会调用工厂函数生成默认值。
from collections import defaultdict
# 统计单词出现次数
words = ['apple', 'banana', 'apple', 'orange', 'banana', 'apple']
count = defaultdict(int) # 默认值为0
for w in words:
count[w] += 1
print(count) # defaultdict(<class 'int'>, {'apple': 3, 'banana': 2, 'orange': 1})
# 分组:按首字母分组
group = defaultdict(list)
for w in words:
group[w].append(w)
print(group) # {'a': ['apple', 'apple', 'apple'], 'b': ['banana', 'banana'], 'o': ['orange']}
嵌套 defaultdict:构建多层字典(如树)
tree = lambda: defaultdict(tree)
data = tree()
data['a']['b']['c'] = 1
# 等价于 {'a': {'b': {'c': 1}}}
2.3 OrderedDict:有序字典(Python 3.7+ 已内置)
背景:Python 3.7 开始,普通字典也保持插入顺序。但 OrderedDict 仍然有独特优势:
- 支持
move_to_end()方法,可将键移到末尾或开头 - 比较时考虑顺序(普通字典比较忽略顺序)
from collections import OrderedDict
od = OrderedDict()
od['z'] = 1
od['a'] = 2
od['b'] = 3
# 将 'z' 移到末尾
od.move_to_end('z')
print(od) # OrderedDict([('a', 2), ('b', 3), ('z', 1)])
# 将 'b' 移到开头
od.move_to_end('b', last=False)
print(od) # OrderedDict([('b', 3), ('a', 2), ('z', 1)])
适用场景:实现 LRU 缓存、需要控制元素顺序的配置管理。
2.4 Counter:计数器
适用场景:统计可哈希对象的出现次数,是 defaultdict(int) 的增强版。
from collections import Counter
# 基础计数
cnt = Counter('abracadabra')
print(cnt) # Counter({'a': 5, 'b': 2, 'r': 2, 'c': 1, 'd': 1})
# 常用方法
cnt.most_common(2) # [('a', 5), ('b', 2)]
cnt.elements() # 返回迭代器,按计数重复元素
cnt.total() # 总元素数(Python 3.10+)
cnt.subtract('ab') # 减去计数
cnt.update('xyz') # 增加计数
# 数学运算
c1 = Counter(a=3, b=1)
c2 = Counter(a=1, b=2)
print(c1 + c2) # Counter({'a': 4, 'b': 3})
print(c1 - c2) # Counter({'a': 2}) # 只保留正数
print(c1 & c2) # Counter({'a': 1, 'b': 1}) # 交集(取最小值)
print(c1 | c2) # Counter({'a': 3, 'b': 2}) # 并集(取最大值)
实战:找出文本中最常见的10个单词
import re
from collections import Counter
text = "Python is great. Python is powerful. Python is easy to learn."
words = re.findall(r'\w+', text.lower())
top10 = Counter(words).most_common(10)
2.5 namedtuple:命名元组
适用场景:创建轻量级、不可变的数据对象,比类更简洁,比字典更高效。
from collections import namedtuple
# 定义
Point = namedtuple('Point', ['x', 'y'])
p = Point(10, 20)
# 访问
print(p.x, p.y) # 10 20
print(p, p) # 10 20(支持索引)
# 解包
x, y = p
# 替换(返回新实例)
p_new = p._replace(x=100)
# 转为字典
print(p._asdict()) # {'x': 10, 'y': 20}
与普通类的对比:
# 传统类
class PointClass:
def __init__(self, x, y):
self.x = x
self.y = y
# namedtuple 更简洁,且自动实现 __repr__、__eq__、__hash__
实战:用 namedtuple 表示数据库记录
User = namedtuple('User', ['id', 'name', 'email'])
users = [
User(1, 'Alice', 'alice@example.com'),
User(2, 'Bob', 'bob@example.com'),
]
# 可以像元组一样排序、哈希
三、综合案例:用枚举+高级容器构建订单管理系统
假设我们要实现一个订单处理系统,需要管理订单状态、统计各状态订单数、维护最近处理记录。
from enum import Enum, auto
from collections import deque, Counter, defaultdict, namedtuple
# 1. 定义订单状态枚举
class OrderStatus(Enum):
PENDING = auto()
PAID = auto()
SHIPPED = auto()
COMPLETED = auto()
CANCELLED = auto()
# 2. 定义订单数据结构
Order = namedtuple('Order', ['id', 'status', 'amount'])
# 3. 订单管理器
class OrderManager:
def __init__(self):
self._orders = {} # id -> Order
self._status_counter = Counter()
self._recent_orders = deque(maxlen=10)
self._status_groups = defaultdict(list)
def add_order(self, order_id, amount):
order = Order(id=order_id, status=OrderStatus.PENDING, amount=amount)
self._orders[order_id] = order
self._status_counter[OrderStatus.PENDING] += 1
self._recent_orders.appendleft(order)
self._status_groups[OrderStatus.PENDING].append(order)
def update_status(self, order_id, new_status):
if order_id not in self._orders:
raise ValueError(f"Order {order_id} not found")
old_order = self._orders[order_id]
new_order = old_order._replace(status=new_status)
self._orders[order_id] = new_order
# 更新计数器
self._status_counter[old_order.status] -= 1
self._status_counter[new_status] += 1
# 更新分组
self._status_groups[old_order.status].remove(old_order)
self._status_groups[new_status].append(new_order)
def get_status_summary(self):
return dict(self._status_counter)
def get_recent_orders(self):
return list(self._recent_orders)
def get_orders_by_status(self, status):
return self._status_groups.get(status, [])
# 使用示例
manager = OrderManager()
manager.add_order(1001, 299.0)
manager.add_order(1002, 59.9)
manager.update_status(1001, OrderStatus.PAID)
print(manager.get_status_summary())
# {OrderStatus.PENDING: 1, OrderStatus.PAID: 1}
这个案例展示了:
- 枚举让状态值具有语义,避免魔法数字
namedtuple提供不可变的数据记录Counter轻松统计各状态数量deque维护最近订单(自动丢弃旧记录)defaultdict(list)实现按状态分组
四、性能与选择建议
| 容器 | 时间复杂度 | 内存 | 适用场景 |
|---|---|---|---|
deque | 两端 O(1),中间 O(n) | 中等 | 队列、栈、滑动窗口 |
defaultdict | 同字典 O(1) | 同字典 | 分组、计数、树 |
OrderedDict | 同字典 O(1) | 略高于字典 | 需要控制顺序或 move_to_end |
Counter | 计数 O(n) | 同字典 | 频率统计、多集合运算 |
namedtuple | 同元组 | 紧凑 | 轻量数据对象、替代类 |
何时使用枚举?
- 当一组常量有明确的逻辑关联(如状态、类型、选项)
- 需要类型安全、可迭代、可序列化
- 避免硬编码字符串或数字
何时使用高级容器?
- 需要高效的两端操作 →
deque - 需要自动处理缺失键 →
defaultdict - 需要频率统计和集合运算 →
Counter - 需要不可变且可命名的数据 →
namedtuple - 需要保持插入顺序且支持重排 →
OrderedDict
五、总结
Python 的 enum 和 collections 模块是标准库中的两颗明珠。枚举让常量变得有身份、有行为,彻底告别魔法数字;而 deque、defaultdict、Counter、namedtuple 等高级容器则针对特定场景提供了更优雅、更高效的解决方案。掌握它们,不仅能提升代码的可读性和健壮性,还能让你在数据处理、系统设计时游刃有余。
下次当你再想写 if status == 1 或 dict.setdefault(key, []).append(value) 时,不妨停下来想一想:是否有一个枚举或高级容器能让你写得更少、想得更清?Python 的优雅,往往就藏在这些标准库的细节之中。
更多推荐
所有评论(0)