当前位置:首页>python>Python 设计模式:迭代器模式——用优雅的方式遍历一切

Python 设计模式:迭代器模式——用优雅的方式遍历一切

  • 2026-10-11 08:15:43
Python 设计模式:迭代器模式——用优雅的方式遍历一切

遍历一个集合,是编程中最常见的操作之一。但如果你需要遍历的不仅仅是列表,而是一棵树、一个文件目录、一段网络流,甚至是一个数据库查询结果呢?

迭代器模式(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)

这种方式的问题在于:

  • 遍历逻辑和集合结构耦合:每次更换遍历方式(比如深度优先改广度优先),都需要修改树结构
  • 客户端需要了解内部实现:调用方必须知道这是一个树形结构,才能正确遍历
  • 多种遍历方式难以共存:你无法同时支持"深度优先"和"广度优先"两种遍历方式

迭代器模式通过引入一个独立的迭代器对象,将"遍历"这个动作从集合本身中剥离出来,完美解决了上述问题。

迭代器模式的结构

迭代器模式主要包含两个角色:

  1. 聚合对象(Aggregate / Iterable):定义创建迭代器的接口,比如 create_iterator()
  2. 迭代器(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 传统迭代器

特性
传统迭代器类
生成器函数
代码量
较多,需定义类和方法
极少,一个函数即可
状态保存
手动维护(self._stack 等)
自动保存,由 Python 解释器处理
内存占用
取决于实现
极低,惰性求值
功能扩展
方便添加额外方法
功能相对单一
适用场景
需要多态或复杂状态管理
简单、线性的遍历逻辑

建议:在 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 中,它不仅仅是一种"设计模式",更是语言的核心机制。

回顾本文要点:

  1. 迭代器模式的核心思想:将遍历逻辑从聚合对象中分离,让两者独立变化
  2. Python 的迭代器协议:__iter__() + __next__() + StopIteration
  3. 自定义迭代器:通过类实现,或使用更简洁的生成器函数
  4. 实战价值:隐藏复杂遍历逻辑(如树遍历、分页查询),提供统一接口
  5. 最佳实践:优先使用生成器,善用 itertools,注意迭代器的一次性特性

掌握了迭代器模式,你不仅能让代码更加解耦和可扩展,还能充分利用 Python 的惰性求值特性,写出更内存友好的程序。


如果这篇文章对你有帮助,欢迎点赞、在看、转发,你的支持是我持续创作的动力!

最新文章

随机文章