遍历一个集合是编程中最常见的操作之一。但如果你需要遍历的不仅仅是列表而是一棵树、一个文件目录、一段网络流甚至是一个数据库查询结果呢迭代器模式Iterator Pattern就是为此而生——它提供一种方法让你在不暴露集合内部结构的前提下逐个访问集合中的元素。在 Python 中迭代器不是一个陌生的设计模式而是融入语言血脉的基础设施。for循环的背后、生成器的底层、甚至是文件对象的可迭代性都是迭代器模式的最佳体现。今天这篇文章我们将从设计模式的视角重新认识 Python 中这个最熟悉的陌生人。一、什么是迭代器模式迭代器模式Iterator Pattern是一种行为型设计模式它提供一种方法来顺序访问聚合对象中的各个元素而无需暴露该对象的内部表示。为什么需要迭代器模式假设你有一个复杂的树形结构比如一个文件系统的目录树classFileNode:def__init__(self,name,is_directoryFalse):self.namename self.is_directoryis_directory self.children[]# 如果是目录包含子节点如果不使用迭代器模式遍历这个树的方法可能会被硬编码在树结构内部# 糟糕的做法遍历逻辑和树结构耦合在一起defprint_all_files(node):ifnotnode.is_directory:print(node.name)else:forchildinnode.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 迭代器协议两个魔法方法一个对象要成为迭代器必须实现以下两个方法classMyIterator:def__iter__(self):返回迭代器对象自身returnselfdef__next__(self):返回下一个元素没有则抛出 StopIteration# ... 返回下一个值或者抛出 StopIterationpass当一个对象同时实现了__iter__()和__next__()它就是一个迭代器。而只实现了__iter__()的对象是一个可迭代对象Iterable。2.2 for 循环的幕后机制当你写下for item in collection:时Python 实际上做了这些事# 这行代码...foritemincollection:print(item)# ... 等价于下面的逻辑_iteratoriter(collection)# 调用 collection.__iter__()whileTrue:try:itemnext(_iterator)# 调用 _iterator.__next__()print(item)exceptStopIteration:break这就是迭代器模式的 Python 实现iter()获取迭代器next()逐个获取元素StopIteration异常标志迭代结束。2.3 Python 中的常见迭代器Python 内置了丰富的迭代器支持# 列表、元组、字符串——基础可迭代对象forcharinPython:print(char)# 字典——遍历键、值、键值对forkey,valuein{a:1,b:2}.items():print(key,value)# 文件对象——逐行读取withopen(data.txt,r,encodingutf-8)asf:forlineinf:# 文件对象本身就是迭代器print(line.strip())# 生成器表达式squares(x**2forxinrange(10))forsqinsquares:print(sq)这些看起来理所当然的语法背后都是迭代器模式在支撑。三、实战为复杂结构实现自定义迭代器接下来我们通过两个实战案例演示如何在 Python 中应用迭代器模式。案例 1深度优先遍历文件系统树让我们回到开头的文件系统树例子这次用迭代器模式来实现fromcollectionsimportdequeclassFileNode:文件系统节点——聚合对象def__init__(self,name,is_directoryFalse):self.namename self.is_directoryis_directory self.children[]defadd_child(self,node):self.children.append(node)def__iter__(self):返回默认的深度优先迭代器returnDepthFirstIterator(self)classDepthFirstIterator:深度优先迭代器def__init__(self,root):self._stack[root]def__iter__(self):returnselfdef__next__(self):ifnotself._stack:raiseStopIteration# 弹出栈顶节点nodeself._stack.pop()# 将子节点压入栈逆序压入保证正序弹出ifnode.is_directory:forchildinreversed(node.children):self._stack.append(child)returnnodeclassBreadthFirstIterator:广度优先迭代器——同样的聚合对象不同的遍历方式def__init__(self,root):self._queuedeque([root])def__iter__(self):returnselfdef__next__(self):ifnotself._queue:raiseStopIteration# 从队列头部取出nodeself._queue.popleft()# 将子节点加入队列尾部ifnode.is_directory:forchildinnode.children:self._queue.append(child)returnnode使用方式# 构建文件系统树rootFileNode(root,is_directoryTrue)root.add_child(FileNode(file1.txt))folder_aFileNode(folder_a,is_directoryTrue)folder_a.add_child(FileNode(file2.txt))folder_a.add_child(FileNode(file3.txt))root.add_child(folder_a)# 使用默认的深度优先迭代器print( 深度优先 )fornodeinroot:print(node.name)# 使用广度优先迭代器print(\n 广度优先 )fornodeinBreadthFirstIterator(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分页数据库查询迭代器在实际业务中迭代器模式最常见的应用场景之一就是分页查询。下面的例子展示了如何用迭代器封装数据库分页逻辑classPaginatedQuery:分页查询迭代器——隐藏分页细节对外表现为连续的数据流def__init__(self,db_connection,query,page_size100):self._dbdb_connection self._queryquery self._page_sizepage_size self._current_page0self._buffer[]self._buffer_index0self._exhaustedFalsedef__iter__(self):returnselfdef__next__(self):# 如果缓冲区还有数据直接返回ifself._buffer_indexlen(self._buffer):resultself._buffer[self._buffer_index]self._buffer_index1returnresult# 缓冲区空了需要加载下一页ifself._exhausted:raiseStopIteration self._load_next_page()# 加载后仍然没有数据说明已耗尽ifnotself._buffer:self._exhaustedTrueraiseStopIteration# 返回新加载的第一个数据resultself._buffer[self._buffer_index]self._buffer_index1returnresultdef_load_next_page(self):从数据库加载下一页数据offsetself._current_page*self._page_size page_queryf{self._query}LIMIT{self._page_size}OFFSET{offset}# 模拟数据库查询实际中替换为真实查询# self._buffer self._db.execute(page_query).fetchall()self._bufferself._mock_query(page_query)self._buffer_index0self._current_page1# 如果返回的数据少于 page_size说明是最后一页iflen(self._buffer)self._page_size:self._exhaustedTruedef_mock_query(self,query):模拟数据查询实际使用时删除此方法importrandomifself._current_page5:# 模拟总共 600 条数据return[]return[frecord_{self._current_page*self._page_sizei}foriinrange(self._page_size)]# 使用示例dbNone# 实际的数据库连接queryPaginatedQuery(db,SELECT * FROM users WHERE status active)# 客户端代码完全感知不到分页的存在forrecordinquery:print(record)# 处理每一条记录...这个例子的精妙之处在于封装复杂性客户端无需关心 OFFSET、LIMIT、页码计算内存友好一次只加载一页数据而非一次性加载全表接口统一无论底层是分页查询还是内存列表使用方式完全一致四、生成器Pythonic 的迭代器在 Python 中手动实现__iter__和__next__虽然可行但通常有更优雅的写法——生成器Generator。4.1 用生成器简化自定义迭代器前面的深度优先遍历用生成器可以大幅简化classFileNode:def__init__(self,name,is_directoryFalse):self.namename self.is_directoryis_directory self.children[]defadd_child(self,node):self.children.append(node)defdepth_first(self):使用生成器实现深度优先遍历yieldself# 先返回自身ifself.is_directory:forchildinself.children:yieldfromchild.depth_first()# 递归遍历子节点defbreadth_first(self):使用生成器实现广度优先遍历fromcollectionsimportdeque queuedeque([self])whilequeue:nodequeue.popleft()yieldnodeifnode.is_directory:queue.extend(node.children)使用生成器后你甚至不需要单独定义DepthFirstIterator和BreadthFirstIterator类yield关键字会自动帮你创建迭代器对象。4.2 生成器 vs 传统迭代器特性传统迭代器类生成器函数代码量较多需定义类和方法极少一个函数即可状态保存手动维护self._stack 等自动保存由 Python 解释器处理内存占用取决于实现极低惰性求值功能扩展方便添加额外方法功能相对单一适用场景需要多态或复杂状态管理简单、线性的遍历逻辑建议在 Python 中优先使用生成器实现迭代器模式除非你需要迭代器支持额外的操作如reset()、peek()等。五、迭代器模式的应用场景迭代器模式在 Python 开发中无处不在以下是一些典型的应用场景场景 1统一遍历不同数据结构# 无论底层是列表、树、图还是数据库都使用相同的 for 循环遍历foritemincollection:process(item)场景 2惰性求值与大数据处理# 处理 10GB 的日志文件无需一次性读入内存withopen(huge.log,r,encodingutf-8)asf:forlineinf:# 每次只读一行ifERRORinline:print(line)场景 3无限序列deffibonacci():无限斐波那契数列a,b0,1whileTrue:yielda a,bb,ab# 取前 10 个fibfibonacci()for_inrange(10):print(next(fib))场景 4组合模式的遍历迭代器模式还可以与组合模式完美配合。当你使用树形结构组织对象时迭代器能为客户端提供统一的遍历接口无需关心底层是树、列表还是其他复合结构。六、最佳实践与注意事项6.1 让对象同时支持多种迭代方式如果聚合对象需要支持多种遍历方式如深度优先/广度优先不要将所有逻辑塞进__iter__()中而是提供不同的方法返回不同的迭代器classTreeNode:def__iter__(self):returnself.depth_first()defdepth_first(self):返回深度优先迭代器...defbreadth_first(self):返回广度优先迭代器...6.2 迭代器的一次性特性Python 的迭代器是一次性的遍历完成后就无法再次使用iteratoriter([1,2,3])print(list(iterator))# [1, 2, 3]print(list(iterator))# [] —— 已经耗尽了如果需要多次遍历应该每次重新调用iter()或返回新的迭代器实例。6.3 不要在遍历过程中修改集合numbers[1,2,3,4,5]forninnumbers:ifn%20:numbers.remove(n)# 危险可能导致不可预期的行为正确做法是先收集需要修改的元素遍历结束后再统一处理或者创建新的集合。6.4 优先使用 itertoolsPython 的itertools模块提供了大量经过优化的迭代器工具应优先使用而非自己实现importitertools# 无限计数器foriinitertools.count(start10,step2):ifi20:breakprint(i)# 10, 12, 14, 16, 18, 20# 循环迭代foriteminitertools.cycle([A,B,C]):# 无限循环 A, B, Cpass# 组合colors[红,绿,蓝]forcomboinitertools.combinations(colors,2):print(combo)# (红, 绿), (红, 蓝), (绿, 蓝)七、总结迭代器模式是 GoF 设计模式中与 Python 融合最深入的模式之一。在 Python 中它不仅仅是一种设计模式更是语言的核心机制。回顾本文要点迭代器模式的核心思想将遍历逻辑从聚合对象中分离让两者独立变化Python 的迭代器协议__iter__()__next__()StopIteration自定义迭代器通过类实现或使用更简洁的生成器函数实战价值隐藏复杂遍历逻辑如树遍历、分页查询提供统一接口最佳实践优先使用生成器善用 itertools注意迭代器的一次性特性掌握了迭代器模式你不仅能让代码更加解耦和可扩展还能充分利用 Python 的惰性求值特性写出更内存友好的程序。如果这篇文章对你有帮助欢迎点赞、在看、转发你的支持是我持续创作的动力