直接实现 __iter__ 和 __next__ 更可控,因其允许手动维护状态、响应外部变化、精确控制异常抛出时机,并支持资源管理与多次迭代;而生成器隐式管理状态机,灵活性较低。

为什么直接实现 __iter__ 和 __next__ 比用生成器更可控?
当你需要在迭代过程中维护状态(比如计数、缓存、资源锁)、响应外部变化,或精确控制异常抛出时机时,手写迭代器协议比 yield 更直接。生成器函数隐式管理状态机,而显式定义这两个方法让你能干预每一步——比如在 __next__ 里做 I/O 检查、重试逻辑,或根据运行时条件提前终止。
常见错误是只实现 __next__ 却忘了返回 self 的 __iter__,导致对象无法用于 for 循环或 list() 构造——Python 迭代协议要求可迭代对象必须返回一个迭代器,而该迭代器自身必须可被迭代(即有 __iter__ 返回自身)。
-
__iter__必须返回一个带有__next__方法的对象(通常是self) -
__next__在耗尽时必须显式抛出StopIteration,不能靠返回None或空值代替 - 如果支持多次迭代(如每次调用
iter(obj)都应得到新迭代器),__iter__就不该返回self,而应返回新实例
__next__ 中如何安全处理边界和异常?
边界检查必须放在 __next__ 开头,且每次调用都重新评估。常见陷阱是把索引递增放在检查前,导致越界访问;或把状态更新(如 self.index += 1)和数据返回混在一起,使最后一次调用仍修改内部状态。
示例:一个读取文件行但跳过空行的迭代器:
class NonEmptyLineIterator:
def __init__(self, path):
self.path = path
self.file = None
self.line_iter = None
<pre class="brush:php;toolbar:false;">def __iter__(self):
# 每次 iter() 都打开新文件,支持多次遍历
self.file = open(self.path, 'r', encoding='utf-8')
self.line_iter = iter(self.file)
return self
def __next__(self):
while True:
try:
line = next(self.line_iter).rstrip('\n\r')
if line: # 跳过空行
return line
except StopIteration:
self.file.close()
raise # 透传,不捕获
快速生成专业的 Python 脚本和应用代码。一键创建完整项目结构,支持CLI、API、爬虫、Bot、Django等多种项目类型,包含完整的项目结构、配置文件、依赖管理、测试、README和文档。
- 避免在
__init__中预加载全部数据,否则失去惰性求值优势 - 资源清理(如
close())应在__next__抛出StopIteration前完成 - 不要在
__next__中静默吞掉StopIteration,这会让循环永远卡住
什么时候该让 __iter__ 返回新对象而不是 self?
当迭代器状态不可重置(比如已读取部分网络流、消耗了生成器、或内部有副作用),就必须每次 iter() 都返回新实例。否则同一对象被多次 for 循环使用时会“耗尽”后始终返回空。
典型场景:封装第三方迭代器(如 itertools.chain)、带缓冲的解析器、或需隔离上下文的数据库游标。
- 若类本身既是可迭代对象又是迭代器(单次使用),
__iter__返回self即可 - 若需支持多轮遍历(如调试时反复
list(it)),__iter__应返回新实例,并在新实例中初始化所有状态 - 注意:返回新实例不等于每次调用都新建底层资源(如重复打开文件),可延迟到首次
__next__再初始化
性能和兼容性要注意哪些细节?
CPython 对内置类型迭代做了高度优化,自定义迭代器在纯 Python 层不可避免有额外开销。但真正影响性能的往往是误用模式,而非协议本身。
- 避免在
__next__中做重复计算(如每次调用都os.stat());把可复用的结果缓存在实例变量中 - 如果底层数据支持随机访问(如列表),别强行用迭代器模拟——直接用索引更高效
- 在 PyPy 或旧版 CPython(__next__ 方法名拼写错误(如写成
next)不会报错,但迭代会失败,务必检查方法名是否带双下划线 - Python 3.12+ 对
StopIteration的处理更严格:从生成器中抛出它会被转为RuntimeError,但手写迭代器不受影响
最常被忽略的是迭代器的“一次性”语义——它不是容器,没有长度、不支持切片、不能回退。如果业务需要这些能力,得另外实现 __len__、__getitem__,或者干脆换用序列协议。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!










