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

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

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

这种方式的问题在于:

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

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

迭代器模式的结构

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

  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 迭代器协议:两个魔法方法

一个对象要成为迭代器,必须实现以下两个方法:

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)

使用生成器后,你甚至不需要单独定义 DepthFirstIteratorBreadthFirstIterator 类!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 中,它不仅仅是一种"设计模式",更是语言的核心机制。

回顾本文要点:

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

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


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

相关推荐
阿豪只会阿巴2 小时前
【没事学点啥】TurboBlog轻量级个人博客项目——Turbo Blog 项目学习与上线指南
开发语言·python·学习·状态模式
飞Link2 小时前
构筑你的数字第二大脑:Obsidian 深度解析与配置指南
开发语言·python
JaydenAI2 小时前
[Deep Agents:LangChain的Agent Harness-02]构建抽象的文件系统
python·langchain·ai编程·ai agent·deep agents·harness
2403_883261092 小时前
如何用 nodeType 与 nodeName 准确判断当前节点的物理类型
jvm·数据库·python
qq_413502022 小时前
如何利用 Block Tree 避免不必要的子组件重渲染?Vue3 编译黑科技
jvm·数据库·python
m0_624578592 小时前
CSS定位如何实现多行文字垂直居中_通过绝对定位模拟表格
jvm·数据库·python
破无差2 小时前
武术套路帖子
python
dfdfadffa3 小时前
mysql如何排查网络延迟引起的数据库连接问题_使用ping测试
jvm·数据库·python
WL_Aurora3 小时前
【每日一题】二分算法
python·算法