在 Python 中,生成器对象在迭代语义中只是一个普通的迭代器,解释器只通过 __iter__ 与 __next__ 推进它的执行。但与此同时,生成器对象具备一些额外的扩展接口,用于对其执行过程进行显式控制。这些接口包括:send()、throw() 与 close()。它们的共同特点是:解释器在迭代语境中不会自动调用它们,它们只服务于用户对执行过程的主动干预。

一、生成器对象的扩展接口不属于迭代协议

在讨论具体接口之前,必须先明确一个边界事实:生成器对象之所以能被 for、next() 等机制驱动,完全是因为它实现了迭代协议。扩展接口的存在与否,不会影响协议是否成立。

也就是说,解释器在任何迭代语义中,都不会检查生成器对象是否具有 send、throw 或 close。

比如:

在整个过程中,解释器既不知道、也不关心生成器是否支持 send() 或 throw()。

二、send(value):向暂停点注入值

send(value) 用于恢复生成器的执行,并将 value 作为上一次暂停处 yield 表达式的结果。

这一接口成立的前提是:生成器必须已经执行到至少一个 yield,从而形成一个可恢复的暂停点。

示例:yield 作为表达式接收外部值

print(g.send("hi"))   # "hi",作为 yield 1 表达式的结果赋给 x

这里的关键不在于“生成器能通信”,而在于yield 暂停的执行帧,在恢复时可以接收一个外部提供的值

一个常见误解是认为 send() 是“next 的增强版”。事实上,send() 的使用受到严格的执行状态约束。

在生成器尚未启动之前,没有执行帧、没有暂停点,也不存在可接收值的 yield 表达式。

因此,首次恢复只能使用 next() 或 send(None)。

示例:未启动生成器时不能直接 send

g.send("hi")           # 合法

这一限制不是语法规则,而是执行模型的直接结果。

三、throw(exc):向执行帧内部注入异常

throw(exc) 用于在生成器当前暂停点,向其执行帧内部抛出一个异常,就好像该异常是在生成器内部发生的一样。

生成器可以选择捕获该异常,或者让其向外传播。

示例:生成器内部捕获外部注入的异常

print(g.throw(ValueError))   # "handled"

从执行语义上看,throw() 所做的只是恢复执行帧,并在恢复点立刻触发一次异常。

对尚未启动的生成器调用 throw() 时,由于尚不存在可注入的暂停点,异常通常会直接向外传播。要让异常在生成器内部被捕获,必须先 next() / send(None) 将其推进到一个暂停点。

如果通过 throw() 注入的异常在生成器内部未被捕获,那么该异常会终止生成器的执行,并向外传播。

示例:异常导致生成器终止

g.throw(RuntimeError)     # RuntimeError 向外抛出,生成器终止

这与普通函数中未捕获异常的行为完全一致,体现了生成器执行模型与普通函数的一致性。

四、close():请求终止执行过程

close() 用于显式请求生成器结束执行,其效果是:向执行帧注入终止信号,使其进入关闭流程。若后续再推进将直接触发 StopIteration。

示例:主动关闭生成器

next(g)           # StopIteration

close() 并不是“清空生成器”,而是请求当前执行过程自行结束,并使其进入不可再推进的终止态。

在生成器内部,close() 的语义等价于抛出 GeneratorExit 异常。

该异常的特殊之处在于:生成器在响应关闭请求时,不允许再产出值。

示例:在 GeneratorExit 中继续 yield 是非法的

        

这一限制明确传达了设计意图:关闭是终止信号,而不是一次普通的异常处理分支。

五、生成器扩展接口的应用示例

当生成器不再只是“被动产出值”,而需要与外部形成“可控的执行协作”时,生成器对象的扩展接口就可真正发挥价值。

也就是说,当执行过程本身成为交互对象,而不仅是数据来源时,扩展接口才具有不可替代的意义。

示例:可中断、可反馈的任务执行器

使用方:

next(t)    # StopIteration

1、next():用于建立执行帧并进入第一个暂停点,这是生成器的启动步骤。

2、send(value):用于将外部状态注入执行过程,使生成器的执行路径能够根据外部反馈发生变化。

3、throw(exc):用于将异常语义直接注入生成器内部,使其以“自身异常”的方式改变执行流。

4、close()(此处未显式调用):在任务被彻底放弃时,可用于立即终止执行并释放执行帧。

需要注意的是,整个过程中,解释器并未参与任何决策,所有控制都发生在用户代码、生成器对象、执行帧之间。

这个示例揭示了一个关键设计事实:生成器扩展接口并不是为了“增强迭代”,而是为了让“执行过程本身”成为可被操控的对象。

在这种模型下,生成器不再只是“数据的生产者”,而是一个协作式执行单元;外部代码不再只是消费者,而是参与者。

小结

生成器对象在迭代协议之外,额外提供了 send()、throw() 与 close() 等扩展接口,用于对其可暂停执行过程进行显式控制。这些接口并不属于解释器在任何语法语境下自动启用的语言级协议,解释器在迭代语义中也不会主动使用它们。它们的存在并非为了扩展迭代规则,而是为了在保持协议最小化的前提下,为“执行过程对象化”这一设计提供更高层次的运行期控制能力。

打开网易新闻 查看精彩图片

点赞有美意,赞赏是鼓励