遍历一个集合,是编程中最常见的操作之一。但如果你需要遍历的不仅仅是列表,而是一棵树、一个文件目录、一段网络流,甚至是一个数据库查询结果呢?
迭代器模式(Iterator Pattern) 就是为此而生------它提供一种方法,让你在不暴露集合内部结构的前提下,逐个访问集合中的元素。
在 Python 中,迭代器不是一个"陌生"的设计模式,而是融入语言血脉的基础设施 。for 循环的背后、生成器的底层、甚至是文件对象的可迭代性,都是迭代器模式的最佳体现。
今天这篇文章,我们将从设计模式的视角,重新认识 Python 中这个"最熟悉的陌生人"。
一、什么是迭代器模式
迭代器模式(Iterator Pattern) 是一种行为型设计模式,它提供一种方法来顺序访问聚合对象中的各个元素,而无需暴露该对象的内部表示。
为什么需要迭代器模式
假设你有一个复杂的树形结构,比如一个文件系统的目录树:
python
class FileNode:
def __init__(self, name, is_directory=False):
self.name = name
self.is_directory = is_directory
self.children = [] # 如果是目录,包含子节点
如果不使用迭代器模式,遍历这个树的方法可能会被硬编码在树结构内部:
python
# 糟糕的做法:遍历逻辑和树结构耦合在一起
def print_all_files(node):
if not node.is_directory:
print(node.name)
else:
for child in node.children:
print_all_files(child)
这种方式的问题在于:
- 遍历逻辑和集合结构耦合:每次更换遍历方式(比如深度优先改广度优先),都需要修改树结构
- 客户端需要了解内部实现:调用方必须知道这是一个树形结构,才能正确遍历
- 多种遍历方式难以共存:你无法同时支持"深度优先"和"广度优先"两种遍历方式
迭代器模式通过引入一个独立的迭代器对象,将"遍历"这个动作从集合本身中剥离出来,完美解决了上述问题。
迭代器模式的结构
迭代器模式主要包含两个角色:
- 聚合对象(Aggregate / Iterable) :定义创建迭代器的接口,比如
create_iterator() - 迭代器(Iterator) :定义访问和遍历元素的接口,通常包含
first()、next()、is_done()、current_item()等方法
在 Python 中,这两个角色被简化为两个协议:
- 可迭代协议(Iterable Protocol) :对象实现
__iter__()方法,返回一个迭代器 - 迭代器协议(Iterator Protocol) :对象实现
__iter__()和__next__()方法
二、Python 的内置迭代器:语言层面的优雅实现
Python 对迭代器模式的支持是语言级别的。理解 Python 的迭代机制,是掌握迭代器模式的第一步。
2.1 迭代器协议:两个魔法方法
一个对象要成为迭代器,必须实现以下两个方法:
python
class MyIterator:
def __iter__(self):
"""返回迭代器对象自身"""
return self
def __next__(self):
"""返回下一个元素,没有则抛出 StopIteration"""
# ... 返回下一个值,或者抛出 StopIteration
pass
当一个对象同时实现了 __iter__() 和 __next__(),它就是一个迭代器 。而只实现了 __iter__() 的对象,是一个可迭代对象(Iterable)。
2.2 for 循环的幕后机制
当你写下 for item in collection: 时,Python 实际上做了这些事:
python
# 这行代码...
for item in collection:
print(item)
# ... 等价于下面的逻辑:
_iterator = iter(collection) # 调用 collection.__iter__()
while True:
try:
item = next(_iterator) # 调用 _iterator.__next__()
print(item)
except StopIteration:
break
这就是迭代器模式的 Python 实现:iter() 获取迭代器,next() 逐个获取元素,StopIteration 异常标志迭代结束。
2.3 Python 中的常见迭代器
Python 内置了丰富的迭代器支持:
python
# 列表、元组、字符串------基础可迭代对象
for char in "Python":
print(char)
# 字典------遍历键、值、键值对
for key, value in {"a": 1, "b": 2}.items():
print(key, value)
# 文件对象------逐行读取
with open("data.txt", "r", encoding="utf-8") as f:
for line in f: # 文件对象本身就是迭代器
print(line.strip())
# 生成器表达式
squares = (x**2 for x in range(10))
for sq in squares:
print(sq)
这些看起来"理所当然"的语法,背后都是迭代器模式在支撑。
三、实战:为复杂结构实现自定义迭代器
接下来,我们通过两个实战案例,演示如何在 Python 中应用迭代器模式。
案例 1:深度优先遍历文件系统树
让我们回到开头的文件系统树例子,这次用迭代器模式来实现:
python
from collections import deque
class FileNode:
"""文件系统节点------聚合对象"""
def __init__(self, name, is_directory=False):
self.name = name
self.is_directory = is_directory
self.children = []
def add_child(self, node):
self.children.append(node)
def __iter__(self):
"""返回默认的深度优先迭代器"""
return DepthFirstIterator(self)
class DepthFirstIterator:
"""深度优先迭代器"""
def __init__(self, root):
self._stack = [root]
def __iter__(self):
return self
def __next__(self):
if not self._stack:
raise StopIteration
# 弹出栈顶节点
node = self._stack.pop()
# 将子节点压入栈(逆序压入,保证正序弹出)
if node.is_directory:
for child in reversed(node.children):
self._stack.append(child)
return node
class BreadthFirstIterator:
"""广度优先迭代器------同样的聚合对象,不同的遍历方式"""
def __init__(self, root):
self._queue = deque([root])
def __iter__(self):
return self
def __next__(self):
if not self._queue:
raise StopIteration
# 从队列头部取出
node = self._queue.popleft()
# 将子节点加入队列尾部
if node.is_directory:
for child in node.children:
self._queue.append(child)
return node
使用方式:
python
# 构建文件系统树
root = FileNode("root", is_directory=True)
root.add_child(FileNode("file1.txt"))
folder_a = FileNode("folder_a", is_directory=True)
folder_a.add_child(FileNode("file2.txt"))
folder_a.add_child(FileNode("file3.txt"))
root.add_child(folder_a)
# 使用默认的深度优先迭代器
print("=== 深度优先 ===")
for node in root:
print(node.name)
# 使用广度优先迭代器
print("\n=== 广度优先 ===")
for node in BreadthFirstIterator(root):
print(node.name)
输出:
=== 深度优先 ===
root
folder_a
file3.txt
file2.txt
file1.txt
=== 广度优先 ===
root
file1.txt
folder_a
file2.txt
file3.txt
关键点:
FileNode聚合对象不再关心如何遍历,只负责返回一个默认迭代器- 客户端可以通过
for node in root:简洁地遍历,无需了解树的内部结构 - 通过更换迭代器,可以轻松切换遍历策略,而无需修改
FileNode的代码
案例 2:分页数据库查询迭代器
在实际业务中,迭代器模式最常见的应用场景之一就是分页查询。下面的例子展示了如何用迭代器封装数据库分页逻辑:
python
class PaginatedQuery:
"""分页查询迭代器------隐藏分页细节,对外表现为连续的数据流"""
def __init__(self, db_connection, query, page_size=100):
self._db = db_connection
self._query = query
self._page_size = page_size
self._current_page = 0
self._buffer = []
self._buffer_index = 0
self._exhausted = False
def __iter__(self):
return self
def __next__(self):
# 如果缓冲区还有数据,直接返回
if self._buffer_index < len(self._buffer):
result = self._buffer[self._buffer_index]
self._buffer_index += 1
return result
# 缓冲区空了,需要加载下一页
if self._exhausted:
raise StopIteration
self._load_next_page()
# 加载后仍然没有数据,说明已耗尽
if not self._buffer:
self._exhausted = True
raise StopIteration
# 返回新加载的第一个数据
result = self._buffer[self._buffer_index]
self._buffer_index += 1
return result
def _load_next_page(self):
"""从数据库加载下一页数据"""
offset = self._current_page * self._page_size
page_query = f"{self._query} LIMIT {self._page_size} OFFSET {offset}"
# 模拟数据库查询(实际中替换为真实查询)
# self._buffer = self._db.execute(page_query).fetchall()
self._buffer = self._mock_query(page_query)
self._buffer_index = 0
self._current_page += 1
# 如果返回的数据少于 page_size,说明是最后一页
if len(self._buffer) < self._page_size:
self._exhausted = True
def _mock_query(self, query):
"""模拟数据查询,实际使用时删除此方法"""
import random
if self._current_page > 5: # 模拟总共 600 条数据
return []
return [f"record_{self._current_page * self._page_size + i}"
for i in range(self._page_size)]
# 使用示例
db = None # 实际的数据库连接
query = PaginatedQuery(db, "SELECT * FROM users WHERE status = 'active'")
# 客户端代码完全感知不到分页的存在!
for record in query:
print(record)
# 处理每一条记录...
这个例子的精妙之处在于:
- 封装复杂性:客户端无需关心 OFFSET、LIMIT、页码计算
- 内存友好:一次只加载一页数据,而非一次性加载全表
- 接口统一:无论底层是分页查询还是内存列表,使用方式完全一致
四、生成器:Pythonic 的迭代器
在 Python 中,手动实现 __iter__ 和 __next__ 虽然可行,但通常有更优雅的写法------生成器(Generator)。
4.1 用生成器简化自定义迭代器
前面的深度优先遍历,用生成器可以大幅简化:
python
class FileNode:
def __init__(self, name, is_directory=False):
self.name = name
self.is_directory = is_directory
self.children = []
def add_child(self, node):
self.children.append(node)
def depth_first(self):
"""使用生成器实现深度优先遍历"""
yield self # 先返回自身
if self.is_directory:
for child in self.children:
yield from child.depth_first() # 递归遍历子节点
def breadth_first(self):
"""使用生成器实现广度优先遍历"""
from collections import deque
queue = deque([self])
while queue:
node = queue.popleft()
yield node
if node.is_directory:
queue.extend(node.children)
使用生成器后,你甚至不需要单独定义 DepthFirstIterator 和 BreadthFirstIterator 类!yield 关键字会自动帮你创建迭代器对象。
4.2 生成器 vs 传统迭代器
| 特性 | 传统迭代器类 | 生成器函数 |
|---|---|---|
| 代码量 | 较多,需定义类和方法 | 极少,一个函数即可 |
| 状态保存 | 手动维护(self._stack 等) | 自动保存,由 Python 解释器处理 |
| 内存占用 | 取决于实现 | 极低,惰性求值 |
| 功能扩展 | 方便添加额外方法 | 功能相对单一 |
| 适用场景 | 需要多态或复杂状态管理 | 简单、线性的遍历逻辑 |
建议 :在 Python 中,优先使用生成器实现迭代器模式,除非你需要迭代器支持额外的操作(如 reset()、peek() 等)。
五、迭代器模式的应用场景
迭代器模式在 Python 开发中无处不在,以下是一些典型的应用场景:
场景 1:统一遍历不同数据结构
python
# 无论底层是列表、树、图还是数据库,都使用相同的 for 循环遍历
for item in collection:
process(item)
场景 2:惰性求值与大数据处理
python
# 处理 10GB 的日志文件,无需一次性读入内存
with open("huge.log", "r", encoding="utf-8") as f:
for line in f: # 每次只读一行
if "ERROR" in line:
print(line)
场景 3:无限序列
python
def fibonacci():
"""无限斐波那契数列"""
a, b = 0, 1
while True:
yield a
a, b = b, a + b
# 取前 10 个
fib = fibonacci()
for _ in range(10):
print(next(fib))
场景 4:组合模式的遍历
迭代器模式还可以与组合模式完美配合。当你使用树形结构组织对象时,迭代器能为客户端提供统一的遍历接口,无需关心底层是树、列表还是其他复合结构。
六、最佳实践与注意事项
6.1 让对象同时支持多种迭代方式
如果聚合对象需要支持多种遍历方式(如深度优先/广度优先),不要将所有逻辑塞进 __iter__() 中,而是提供不同的方法返回不同的迭代器:
python
class TreeNode:
def __iter__(self):
return self.depth_first()
def depth_first(self):
"""返回深度优先迭代器"""
...
def breadth_first(self):
"""返回广度优先迭代器"""
...
6.2 迭代器的"一次性"特性
Python 的迭代器是一次性的,遍历完成后就无法再次使用:
python
iterator = iter([1, 2, 3])
print(list(iterator)) # [1, 2, 3]
print(list(iterator)) # [] ------ 已经耗尽了!
如果需要多次遍历,应该每次重新调用 iter() 或返回新的迭代器实例。
6.3 不要在遍历过程中修改集合
python
numbers = [1, 2, 3, 4, 5]
for n in numbers:
if n % 2 == 0:
numbers.remove(n) # 危险!可能导致不可预期的行为
正确做法是先收集需要修改的元素,遍历结束后再统一处理,或者创建新的集合。
6.4 优先使用 itertools
Python 的 itertools 模块提供了大量经过优化的迭代器工具,应优先使用而非自己实现:
python
import itertools
# 无限计数器
for i in itertools.count(start=10, step=2):
if i > 20:
break
print(i) # 10, 12, 14, 16, 18, 20
# 循环迭代
for item in itertools.cycle(["A", "B", "C"]):
# 无限循环 A, B, C
pass
# 组合
colors = ["红", "绿", "蓝"]
for combo in itertools.combinations(colors, 2):
print(combo) # ('红', '绿'), ('红', '蓝'), ('绿', '蓝')
七、总结
迭代器模式是 GoF 设计模式中与 Python 融合最深入的模式之一。在 Python 中,它不仅仅是一种"设计模式",更是语言的核心机制。
回顾本文要点:
- 迭代器模式的核心思想:将遍历逻辑从聚合对象中分离,让两者独立变化
- Python 的迭代器协议 :
__iter__()+__next__()+StopIteration - 自定义迭代器:通过类实现,或使用更简洁的生成器函数
- 实战价值:隐藏复杂遍历逻辑(如树遍历、分页查询),提供统一接口
- 最佳实践:优先使用生成器,善用 itertools,注意迭代器的一次性特性
掌握了迭代器模式,你不仅能让代码更加解耦 和可扩展 ,还能充分利用 Python 的惰性求值特性,写出更内存友好的程序。
如果这篇文章对你有帮助,欢迎点赞、在看、转发,你的支持是我持续创作的动力!