遍历一个集合,是编程中最常见的操作之一。但如果你需要遍历的不仅仅是列表,而是一棵树、一个文件目录、一段网络流,甚至是一个数据库查询结果呢?
迭代器模式(Iterator Pattern) 就是为此而生——它提供一种方法,让你在不暴露集合内部结构的前提下,逐个访问集合中的元素。
在 Python 中,迭代器不是一个"陌生"的设计模式,而是融入语言血脉的基础设施。for 循环的背后、生成器的底层、甚至是文件对象的可迭代性,都是迭代器模式的最佳体现。
今天这篇文章,我们将从设计模式的视角,重新认识 Python 中这个"最熟悉的陌生人"。
一、什么是迭代器模式
迭代器模式(Iterator Pattern) 是一种行为型设计模式,它提供一种方法来顺序访问聚合对象中的各个元素,而无需暴露该对象的内部表示。
为什么需要迭代器模式
假设你有一个复杂的树形结构,比如一个文件系统的目录树:
class FileNode: def __init__(self, name, is_directory=False): self.name = name self.is_directory = is_directory self.children = [] # 如果是目录,包含子节点
如果不使用迭代器模式,遍历这个树的方法可能会被硬编码在树结构内部:
# 糟糕的做法:遍历逻辑和树结构耦合在一起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 迭代器协议:两个魔法方法
一个对象要成为迭代器,必须实现以下两个方法:
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 实际上做了这些事:
# 这行代码...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 内置了丰富的迭代器支持:
# 列表、元组、字符串——基础可迭代对象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:深度优先遍历文件系统树
让我们回到开头的文件系统树例子,这次用迭代器模式来实现:
from collections import dequeclass 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 nodeclass 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
使用方式:
# 构建文件系统树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)
输出:
=== 深度优先 ===rootfolder_afile3.txtfile2.txtfile1.txt=== 广度优先 ===rootfile1.txtfolder_afile2.txtfile3.txt
关键点:
FileNode 聚合对象不再关心如何遍历,只负责返回一个默认迭代器- 客户端可以通过
for node in root: 简洁地遍历,无需了解树的内部结构 - 通过更换迭代器,可以轻松切换遍历策略,而无需修改
FileNode 的代码
案例 2:分页数据库查询迭代器
在实际业务中,迭代器模式最常见的应用场景之一就是分页查询。下面的例子展示了如何用迭代器封装数据库分页逻辑:
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 用生成器简化自定义迭代器
前面的深度优先遍历,用生成器可以大幅简化:
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 传统迭代器
建议:在 Python 中,优先使用生成器实现迭代器模式,除非你需要迭代器支持额外的操作(如 reset()、peek() 等)。
五、迭代器模式的应用场景
迭代器模式在 Python 开发中无处不在,以下是一些典型的应用场景:
场景 1:统一遍历不同数据结构
# 无论底层是列表、树、图还是数据库,都使用相同的 for 循环遍历for item in collection: process(item)
场景 2:惰性求值与大数据处理
# 处理 10GB 的日志文件,无需一次性读入内存with open("huge.log", "r", encoding="utf-8") as f: for line in f: # 每次只读一行 if "ERROR" in line: print(line)
场景 3:无限序列
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__() 中,而是提供不同的方法返回不同的迭代器:
<br class="Apple-interchange-newline"><div></div>class TreeNode: def __iter__(self): return self.depth_first() def depth_first(self): """返回深度优先迭代器""" ... def breadth_first(self): """返回广度优先迭代器""" ...
6.2 迭代器的"一次性"特性
Python 的迭代器是一次性的,遍历完成后就无法再次使用:
iterator = iter([1, 2, 3])print(list(iterator)) # [1, 2, 3]print(list(iterator)) # [] —— 已经耗尽了!
如果需要多次遍历,应该每次重新调用 iter() 或返回新的迭代器实例。
6.3 不要在遍历过程中修改集合
numbers = [1, 2, 3, 4, 5]for n in numbers: if n % 2 == 0: numbers.remove(n) # 危险!可能导致不可预期的行为
正确做法是先收集需要修改的元素,遍历结束后再统一处理,或者创建新的集合。
6.4 优先使用 itertools
Python 的 itertools 模块提供了大量经过优化的迭代器工具,应优先使用而非自己实现:
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 的惰性求值特性,写出更内存友好的程序。
如果这篇文章对你有帮助,欢迎点赞、在看、转发,你的支持是我持续创作的动力!