Python 设计模式——迭代器模式(Iterator Pattern)详解
本文面向零基础新手,从概念到“遍历与集合解耦”,从 Python 的 for 与协议到自定义迭代器,全方位讲解迭代器模式。
一、什么是迭代器模式?
1.1 通俗理解
迭代器模式是一种行为型设计模式,核心思想是:
把“遍历集合元素”这件事从集合本身拆出来,交给一个独立的“迭代器”对象去做;客户通过迭代器按顺序访问元素,而不必关心集合内部是怎么存的(数组、链表、树、散列表等)。这样集合负责“装数据”,迭代器负责“按某种顺序一个一个拿出来”,两者解耦,且可以针对同一集合提供多种遍历方式。
可以这样类比:
- 不用迭代器:你要遍历一本书的每一页,就得知道书是“第几页到第几页”、怎么翻;若书换成“按章节存的电子书”,你又得写另一套“按章节取”的代码。遍历逻辑和“书怎么存”绑在一起。
- 用迭代器:书(集合)提供一个“迭代器”:你只问迭代器“还有下一页吗?”“给我下一页”;迭代器内部知道书的结构,按顺序一页一页给你。你不需要知道书是数组还是链表,只要会问“下一个”就行。
所以,迭代器模式解决的是:统一、安全地遍历各种集合,而不暴露集合内部结构,并支持多种遍历方式(正序、逆序、过滤等)。
1.2 为什么需要迭代器模式?
场景一:集合内部结构不想暴露
客户只关心“按顺序拿元素”,不关心集合是数组、链表还是树。若直接暴露内部结构(如 list 下标、树节点),会破坏封装且难以更换实现。迭代器对外只提供“下一个元素”“是否还有”等接口,内部结构被隐藏。
场景二:统一遍历方式
列表、字典、树、自定义容器,遍历方式各不相同;若每种都写一套 for 逻辑,客户代码会充满 if-else。用迭代器后,客户只写“用迭代器取下一个”,所有集合只要提供迭代器,就可以用同一套遍历代码(如 for 循环)。
场景三:多种遍历方式
同一集合有时要“正序”,有时要“逆序”,有时要“只遍历偶数下标”。若遍历逻辑写在集合里,会塞满各种方法;拆成多种迭代器(正序迭代器、逆序迭代器、过滤迭代器),集合只负责创建对应的迭代器,更清晰。
1.3 适用场景(什么时候用?)
| 场景 | 说明 |
|---|---|
| 统一遍历多种集合 | 列表、树、图、自定义容器,用同一套“取下一个”的接口。 |
| 隐藏集合内部结构 | 不暴露下标、节点指针等,只通过迭代器访问。 |
| 支持多种遍历 | 正序、逆序、按条件过滤等,每种一个迭代器类。 |
简单记忆:要按顺序访问集合元素且希望不依赖内部实现、可多种遍历时,用迭代器模式。
二、迭代器模式的结构(两个角色)
| 角色 | 说明 |
|---|---|
| 迭代器(Iterator) | 定义“取下一个”“是否还有”等接口;具体实现里按某种顺序访问集合元素。 |
| 集合/聚合(Aggregate) | 负责存储元素,并提供一个方法(如 create_iterator() 或 __iter__())返回迭代器,让客户通过迭代器遍历。 |
关系可以理解为:
- 客户不直接操作集合内部,而是先向集合要一个迭代器,再用迭代器逐个取元素。
- 迭代器内部会持有对集合的引用(或集合内部数据的副本/视图),在
next()时按约定顺序返回下一个元素。
三、Python 自带的迭代器协议(重要)
在 Python 里,迭代器模式已经内置到语言中,用迭代器协议实现:
- 可迭代对象(Iterable):有
__iter__()方法,返回一个迭代器;例如 list、dict、自定义容器。 - 迭代器(Iterator):有
__next__()方法,每次返回下一个元素,没有时抛出StopIteration;通常也有__iter__()并返回self,这样迭代器本身也可以被 for 使用。 - for 循环:
for x in obj会先调obj.__iter__()得到迭代器,再反复调迭代器的__next__()直到StopIteration。
因此:让你的类实现 __iter__ 和 __next__(或只实现 __iter__ 并返回一个带 __next__ 的对象),就实现了迭代器模式,并可直接用于 for。
四、示例一:自定义集合与迭代器(显式写法)
需求:一个简单集合类 MyList,内部用 list 存数据;提供一个迭代器,按顺序遍历。先用“显式”的迭代器类写,便于理解角色。
4.1 迭代器接口 + 具体迭代器
from abc import ABC, abstractmethod
class Iterator(ABC):
@abstractmethod
def has_next(self) -> bool:
pass
@abstractmethod
def next(self):
pass
class MyListIterator(Iterator):
def __init__(self, data: list):
self._data = data
self._index = 0
def has_next(self) -> bool:
return self._index < len(self._data)
def next(self):
if not self.has_next():
raise StopIteration
val = self._data[self._index]
self._index += 1
return val
4.2 集合:提供创建迭代器的方法
class MyList:
"""集合:内部存数据,对外提供迭代器"""
def __init__(self):
self._data = []
def add(self, x):
self._data.append(x)
def create_iterator(self) -> MyListIterator:
return MyListIterator(self._data)
4.3 客户通过迭代器遍历
coll = MyList()
coll.add(1)
coll.add(2)
coll.add(3)
it = coll.create_iterator()
while it.has_next():
print(it.next()) # 1, 2, 3
要点:客户只依赖迭代器的 has_next 和 next,不依赖 MyList 内部是 list 还是别的结构。
五、示例二:实现 Python 的 iter 与 next(用于 for)
需求:让自定义集合支持 for x in collection,并让迭代器符合 Python 协议。
5.1 集合实现 iter,返回迭代器
class MyList:
def __init__(self):
self._data = []
def add(self, x):
self._data.append(x)
def __iter__(self):
"""返回迭代器,用于 for 循环"""
return _MyListIterator(self._data)
class _MyListIterator:
"""内部迭代器:实现 __next__,供 for 使用"""
def __init__(self, data: list):
self._data = data
self._index = 0
def __iter__(self):
return self # 迭代器自己也实现 __iter__,返回 self
def __next__(self):
if self._index >= len(self._data):
raise StopIteration
val = self._data[self._index]
self._index += 1
return val
5.2 使用 for
coll = MyList()
coll.add("a")
coll.add("b")
coll.add("c")
for x in coll:
print(x) # a, b, c
要点:for x in coll 会调 coll.__iter__() 得到迭代器,再反复调迭代器的 __next__();没有元素时迭代器抛 StopIteration,for 自动结束。
六、示例三:逆序迭代器(多种遍历方式)
需求:同一集合,有时要正序,有时要逆序;集合提供两种迭代器(或两种方法返回不同迭代器)。
class MyList:
def __init__(self):
self._data = []
def add(self, x):
self._data.append(x)
def __iter__(self):
return iter(self._data) # 正序:直接用 list 的迭代器
def reverse_iterator(self):
"""逆序迭代器"""
return _ReverseIterator(self._data)
class _ReverseIterator:
def __init__(self, data: list):
self._data = data
self._index = len(data) - 1
def __iter__(self):
return self
def __next__(self):
if self._index < 0:
raise StopIteration
val = self._data[self._index]
self._index -= 1
return val
# 使用
coll = MyList()
for i in [1, 2, 3]:
coll.add(i)
for x in coll:
print(x) # 1, 2, 3
for x in coll.reverse_iterator():
print(x) # 3, 2, 1
要点:集合不暴露内部存储,正序用 for in coll,逆序用 reverse_iterator(),多种遍历方式由不同迭代器提供。
七、示例四:过滤迭代器(只遍历满足条件的元素)
需求:在遍历时只返回满足条件的元素,例如“只返回偶数”。
class FilterIterator:
"""只迭代满足条件的元素"""
def __init__(self, data: list, condition):
self._data = data
self._condition = condition # 函数:elem -> bool
self._index = 0
def __iter__(self):
return self
def __next__(self):
while self._index < len(self._data):
val = self._data[self._index]
self._index += 1
if self._condition(val):
return val
raise StopIteration
# 使用
data = [1, 2, 3, 4, 5, 6]
for x in FilterIterator(data, lambda x: x % 2 == 0):
print(x) # 2, 4, 6
要点:迭代器内部跳过不满足条件的元素,客户拿到的就是“过滤后的序列”;集合本身不变,多种过滤可由不同 FilterIterator 实现。
八、示例五:迭代器本身也可迭代(iter() 与 next() 内置函数)
Python 内置的 iter(obj) 会调 obj.__iter__(),next(it) 会调 it.__next__()。因此:
coll = MyList()
coll.add(10)
coll.add(20)
it = iter(coll) # 等价于 coll.__iter__()
print(next(it)) # 10
print(next(it)) # 20
# next(it) # 抛 StopIteration
要点:只要实现了协议,就可以用 iter()、next() 和 for,无需自己写 create_iterator() 或 has_next()(Python 习惯用 __next__ + StopIteration 表示“没有下一个”)。
九、迭代器模式 vs 其他
| 模式 | 目的 | 与迭代器的区别 |
|---|---|---|
| 迭代器 | 统一、顺序地访问集合元素,不暴露内部结构 | 强调“遍历”“下一个”“集合与遍历解耦”。 |
| 组合 | 树形结构、统一接口、递归操作 | 组合的遍历常通过迭代器实现(如遍历子节点);迭代器是“怎么遍历”,组合是“结构是什么”。 |
| 访问者 | 对结构中的每个元素执行某种操作 | 访问者关心“对每个元素做什么”;迭代器关心“按什么顺序取元素”。 |
简单记忆:迭代器 = 按顺序取元素 + 不暴露集合内部 + 可多种遍历。
十、常见问题与注意点
10.1 迭代器用完后能否重置?
一般不做重置;若要再遍历一遍,重新向集合要一个新的迭代器(如再调一次 __iter__())。这样每个迭代器保持自己的“当前位置”,互不干扰。
10.2 遍历时能否修改集合?
若在 for 循环中增删集合元素,可能导致未定义行为或漏掉/重复元素。通常约定:遍历过程中不要修改原集合;若必须,可先复制一份再遍历,或使用“快照”迭代器。
10.3 生成器(generator)是不是迭代器?
是的。用 yield 的函数会返回一个生成器对象,该对象实现了 __iter__ 和 __next__,所以生成器就是一种迭代器;不需要手写迭代器类也能实现“按需产生下一个值”,是 Python 里非常常用的写法。
十一、小结
- 迭代器模式:把遍历集合元素从集合中拆出,由迭代器负责“下一个”“是否结束”;客户通过迭代器顺序访问,不依赖集合内部结构,并可支持多种遍历方式。
- 两个角色:迭代器(next/has_next 或
__next__)、集合(提供迭代器,如__iter__)。 - Python 协议:实现
__iter__(返回迭代器)和__next__(返回下一元素,无则抛 StopIteration),即可用for、iter()、next()。 - 典型用途:自定义集合的遍历、正序/逆序/过滤等多种遍历、隐藏内部实现。
建议先写一个带 __iter__ 和 __next__ 的自定义集合与迭代器并用于 for,再试逆序或过滤迭代器,这样对迭代器模式会掌握得比较扎实。