Python 基础体系 · 第 16/112 篇。示例统一以 Python 3.14 为语言基线;第三方库使用与其兼容的现代稳定版本,版本敏感行为会单独说明。
Python 生成器:yield、send、throw、close 与委托
生成器(generator)是能够暂停执行、保存现场,并在之后继续执行的迭代器。它既可以作为普通的惰性数据源,也可以通过 send()、throw() 和 close() 接收外部输入、异常与终止信号。
理解生成器不能只记住“yield 会返回一个值”。更准确的模型是:
生成器函数定义了一段可暂停的控制流;生成器对象保存这段控制流的运行现场;调用者通过
next()、send()、throw()和close()驱动它在不同状态之间转换。
Python 3.14 的语言参考将生成器函数、生成器对象和异步生成器分别定义在 yield 表达式与异步生成器相关章节中。普通生成器通过 __next__()、send()、throw() 和 close() 与调用者交互。(docs.python.org)
生成器函数与生成器对象
包含 yield 表达式的普通函数会被定义为生成器函数:
def numbers():
yield 1
yield 2
yield 3
调用它时,函数体并不会立即执行:
iterator = numbers()
print(iterator)
print(type(iterator))
典型输出类似:
<generator object numbers at 0x...>
<class 'generator'>
这里有两个不同的对象:
numbers是生成器函数;numbers()返回一个生成器对象;- 生成器对象同时也是迭代器,支持
__iter__()和__next__()。
调用生成器函数只创建并返回生成器对象。真正执行从第一次调用 next(iterator)、iterator.send(None)、iterator.throw(...) 或 iterator.close() 开始。(docs.python.org)
def demo():
print("开始执行")
yield 10
print("恢复执行")
yield 20
print("执行结束")
g = demo()
print("生成器已创建")
print(next(g))
print(next(g))
try:
next(g)
except StopIteration:
print("生成器已耗尽")
输出:
生成器已创建
开始执行
10
恢复执行
20
执行结束
生成器已耗尽
执行顺序是:
demo()创建生成器对象,没有打印任何内容;- 第一次
next(g)从函数开头执行; - 执行到第一个
yield 10,产生10并暂停; - 第二次
next(g)从上一次暂停的位置继续; - 执行到第二个
yield 20,产生20并再次暂停; - 第三次
next(g)继续执行,函数正常结束; - 生成器以
StopIteration表示耗尽。
暂停时,生成器会保留局部变量绑定、指令位置、求值栈以及异常处理状态,因此恢复后并不是重新调用函数,而是继续原来的执行现场。(docs.python.org)
yield 的双向数据流
最简单的写法是:
yield value
它向调用者产生 value。但是 yield 同时也是一个表达式:
received = yield produced
这行代码有两个方向的数据流:
生成器 调用者
| |
| -- produced ---------------> |
| |
| <--------- send(value) ----- |
| |
| yield 表达式的结果为 value |
第一次执行到 yield produced 时:
produced被返回给调用者;- 生成器暂停;
received尚未赋值。
当调用者之后使用 send(value) 恢复生成器时:
- 当前
yield表达式的结果变成value; - 因此
received == value; - 生成器从赋值语句之后继续运行。
Python 官方文档将这种机制描述为:send(value) 传入的值会成为当前暂停位置的 yield 表达式的结果;如果使用 next() 恢复,结果则是 None。(docs.python.org)
完整示例
def accumulator():
total = 0
while True:
value = yield total
if value is None:
return
total += value
g = accumulator()
print(next(g)) # 启动生成器,并获取初始 total
print(g.send(5)) # 当前 yield 表达式得到 5
print(g.send(7)) # 当前 yield 表达式得到 7
输出:
0
5
12
逐步分析:
第一次调用 next(g)
生成器执行:
total = 0
value = yield total
yield total 产生 0,生成器暂停。此时:
total = 0
value 尚未赋值
调用 g.send(5)
send(5) 恢复上一次暂停的 yield 表达式:
value = 5
随后执行:
total += value
于是:
total = 5
循环再次执行到:
value = yield total
产生 5 并暂停。
调用 g.send(7)
同理:
value = 7
total = 5 + 7 = 12
下一次产生 12。
因此,yield 可以看作一个“反向函数调用”:
result = function(argument)
通常是调用者传入 argument,函数返回 result。而生成器中的:
received = yield produced
则是:
- 生成器先产生
produced; - 调用者恢复时传入
value; - 生成器内部得到
received。
next() 与 send(None) 的关系
对于已经暂停在某个 yield 处的生成器:
next(g)
等价于:
g.send(None)
两者都会让当前 yield 表达式的结果变成 None。(peps.python.org)
但是,生成器刚创建时还没有执行到任何 yield,不存在可以接收传入值的暂停点。因此,第一次启动只能使用:
next(g)
或者:
g.send(None)
不能直接传入非 None 值:
def echo():
value = yield
print(value)
g = echo()
g.send("hello")
结果是:
TypeError: can't send non-None value to a just-started generator
正确写法:
g = echo()
next(g)
g.send("hello")
这里的 next(g) 不表示“丢弃了一次业务数据”,而是完成了生成器的初始化,使它到达第一个能够接收输入的 yield。
yield、return 与 StopIteration
生成器中的 yield 表示暂时产生值并暂停,而 return 表示结束生成器。
def sample():
yield "first"
return "final"
使用 next() 驱动:
g = sample()
print(next(g))
try:
next(g)
except StopIteration as exc:
print("异常类型:", type(exc).__name__)
print("返回值:", exc.value)
输出:
first
异常类型: StopIteration
返回值: final
生成器执行 return value 后,会以 StopIteration(value) 结束;其中 value 可通过异常对象的 .value 属性取得。这个返回值通常在 yield from 委托中最有用。(peps.python.org)
需要区分两种“停止”:
def ordinary():
yield 1
调用第二次 next() 时,生成器正常结束,抛出 StopIteration。
但如果生成器内部直接写:
def invalid():
yield 1
raise StopIteration("bad")
在现代 Python 中,这不会被当成普通的生成器结束值。由生成器代码直接冒出的 StopIteration 会被转换为 RuntimeError,这是为了避免迭代器内部错误被误认为正常耗尽。(peps.python.org)
因此:
return "result"
是生成器返回最终结果的正确方式,而不是:
raise StopIteration("result")
生成器的状态
生成器至少可以处于以下四种可观察状态:
| 状态 | 含义 |
|---|---|
GEN_CREATED |
已创建,但尚未开始执行 |
GEN_RUNNING |
当前正在解释器中执行 |
GEN_SUSPENDED |
暂停在某个 yield 表达式处 |
GEN_CLOSED |
已正常结束、异常结束或被关闭 |
inspect.getgeneratorstate() 可以读取这些状态。(docs.python.org)
import inspect
def task():
yield "ready"
g = task()
print(inspect.getgeneratorstate(g))
print(next(g))
print(inspect.getgeneratorstate(g))
try:
next(g)
except StopIteration:
pass
print(inspect.getgeneratorstate(g))
输出:
GEN_CREATED
ready
GEN_SUSPENDED
GEN_CLOSED
GEN_RUNNING 通常只能在生成器自身执行期间观察。例如:
import inspect
def show_state():
print(inspect.getgeneratorstate(g))
yield
g = show_state()
next(g)
在生成器执行到 print() 时,g 处于 GEN_RUNNING;执行到 yield 后变为 GEN_SUSPENDED。
同一个生成器不能被并发或递归地重复驱动:
def recursive():
yield g.send(None)
g = recursive()
如果生成器在自身执行期间再次调用自己的 send()、throw() 或 next(),通常会得到:
ValueError: generator already executing
这不是线程同步机制。生成器的暂停与恢复提供的是控制流机制,不会自动让多个线程安全地共享一个生成器。
send():向暂停点传入值
send(value) 的协议可以形式化为:
设生成器当前暂停在:
result = yield produced
则:
g.send(value)
执行后相当于让:
result = value
然后继续运行,直到:
- 再次遇到
yield,返回新的产生值; - 生成器结束,抛出
StopIteration; - 生成器抛出未处理异常,该异常传播给调用者。
示例:
def protocol():
print("step 1")
command = yield "waiting"
print("received:", command)
if command == "stop":
return "stopped"
yield "continue"
return "finished"
g = protocol()
print(next(g))
print(g.send("go"))
try:
g.send("stop")
except StopIteration as exc:
print("result:", exc.value)
输出:
step 1
waiting
received: go
continue
result: stopped
注意,send() 的返回值不是“生成器的返回值”,而是生成器恢复后下一次 yield 产生的值。只有生成器结束并抛出 StopIteration 时,才通过异常的 .value 获取 return 的结果。
一个常见误解
下面两段代码并不等价:
yield value
和:
result = yield value
前者忽略恢复时传入的值;后者会把该值接收下来。
def ignore_input():
while True:
yield "constant"
def receive_input():
while True:
value = yield "waiting"
print("got:", value)
调用:
g1 = ignore_input()
next(g1)
print(g1.send(123)) # 仍然产生 constant
g2 = receive_input()
next(g2)
print(g2.send(123)) # 打印 got: 123,然后产生 waiting
send() 只是向当前 yield 表达式提供结果。生成器是否使用这个结果,取决于生成器代码本身。
throw():在暂停点注入异常
throw() 不会在调用者当前位置执行 raise,而是恢复生成器,并在生成器上一次暂停的 yield 位置引发指定异常。
def resilient():
while True:
try:
value = yield "ready"
except ValueError as exc:
print("handled:", exc)
yield "recovered"
g = resilient()
print(next(g))
print(g.throw(ValueError("bad input")))
print(g.send(10))
输出:
ready
handled: bad input
recovered
ready
过程如下:
-
next(g)使生成器暂停在:value = yield "ready" -
g.throw(ValueError("bad input"))在这个yield位置引发异常; -
异常被
except ValueError捕获; -
生成器执行
yield "recovered"; -
throw()返回"recovered"; -
下一次
send(10)从新的yield恢复; -
循环回到原来的位置,再次产生
"ready"。
如果生成器不捕获这个异常,异常就会传播到 throw() 的调用者:
def simple():
yield "ready"
g = simple()
next(g)
try:
g.throw(RuntimeError("failure"))
except RuntimeError as exc:
print("caller caught:", exc)
输出:
caller caught: failure
如果生成器在处理异常后正常结束,throw() 会抛出 StopIteration,而不是返回一个普通值:
def stop_on_error():
try:
yield "ready"
except ValueError:
return "done"
g = stop_on_error()
next(g)
try:
g.throw(ValueError("invalid"))
except StopIteration as exc:
print(exc.value)
输出:
done
throw() 的常用形式是传入异常实例:
g.throw(ValueError("invalid"))
Python 3.14 仍支持历史形式:
g.throw(ValueError, ValueError("invalid"), traceback)
但三参数形式已经在 Python 3.12 中标记为弃用,新的代码应优先使用异常实例形式。(docs.python.org)
向尚未启动的生成器调用 throw()
throw() 可以作用于尚未启动的生成器,但异常会在生成器函数入口处注入。生成器如果没有在入口附近捕获它,就不会执行到后面的代码:
def startup():
try:
yield "ready"
except ValueError:
yield "recovered"
g = startup()
print(g.throw(ValueError("before start")))
输出:
recovered
这与 send() 不同:send(non_none) 在尚未启动时非法,而 throw() 可以直接把异常送入生成器。
close():请求生成器终止
close() 用于向生成器注入特殊异常 GeneratorExit:
g.close()
语义上近似于:
g.throw(GeneratorExit)
但 close() 对 GeneratorExit 有专门的处理规则:
- 生成器抛出
GeneratorExit,关闭成功; - 生成器正常结束,关闭成功;
- 生成器在收到
GeneratorExit后又产生值,抛出RuntimeError; - 生成器抛出其他异常,该异常传播给调用者;
- 已经结束的生成器再次
close(),通常什么也不做并返回None。
这些规则由 Python 语言参考明确规定。Python 3.13 起,如果生成器在关闭过程中通过 return value 返回值,close() 会返回该值;这属于 Python 3.13 引入的行为,在 Python 3.14 中仍然适用。(docs.python.org)
使用 finally 清理资源
def resource_user():
print("acquire")
try:
yield "using"
finally:
print("release")
g = resource_user()
print(next(g))
g.close()
输出:
acquire
using
release
close() 在暂停的 yield 位置引发 GeneratorExit,因此控制流会进入 finally。这使生成器可以把清理逻辑放在 finally 中。
不应吞掉 GeneratorExit 后继续产生值
错误示例:
def broken():
try:
yield "data"
except GeneratorExit:
yield "another-data"
g = broken()
print(next(g))
try:
g.close()
except RuntimeError as exc:
print(type(exc).__name__, exc)
输出类似:
RuntimeError generator ignored GeneratorExit
收到 GeneratorExit 后,生成器只能:
def correct():
try:
yield "data"
finally:
print("cleanup")
或者显式重新抛出:
def also_correct():
try:
yield "data"
except GeneratorExit:
print("cleanup")
raise
不能捕获 GeneratorExit 后继续执行普通的 yield。否则调用者已经要求生成器终止,而生成器却重新向外产生数据,协议状态就发生冲突。
close() 与垃圾回收不是同一件事
生成器对象被垃圾回收时,Python 可能调用它的 close(),从而使挂起的 finally 有机会执行。但是,“对象不再被引用”与“清理立即发生”不是所有 Python 实现都保证的等价关系。
CPython 通常使用引用计数,因此最后一个引用消失后往往很快触发清理;但这属于常见实现行为,不应当被当成跨实现的及时性保证。非引用计数实现可能延迟垃圾回收,循环引用也可能延迟生成器终结。(peps.python.org)
因此,涉及文件、锁、数据库连接或事务时,应显式关闭生成器,或把生命周期放进 try/finally、上下文管理器等明确结构中:
g = resource_user()
try:
print(next(g))
finally:
g.close()
生成器的自动终结适合作为兜底机制,不适合作为主要资源管理协议。
yield from:把控制权委托给下层迭代器
yield from iterable 称为委托(delegation)。
它不是简单的:
for item in iterable:
yield item
在只使用 next() 的情况下,两者经常表现相同:
def manual():
for item in [1, 2, 3]:
yield item
def delegated():
yield from [1, 2, 3]
但是,一旦使用 send()、throw()、close() 或下层生成器的返回值,两者的语义就不同。
yield from 会建立一个“委托生成器—子迭代器”关系:
调用者
|
| next / send / throw / close
v
委托生成器
|
| 转发控制操作
v
子迭代器
Python 语言参考规定:
- 子迭代器产生的值直接交给外部调用者;
- 外部
send()的值转发给子迭代器; - 外部
throw()的异常转发给子迭代器的throw(); - 外部关闭委托生成器时,若子迭代器有
close(),也会先关闭子迭代器; - 子迭代器结束时,其
StopIteration.value成为yield from表达式的结果。(peps.python.org)
yield from 的返回值
先看子生成器:
def child():
yield "child: one"
yield "child: two"
return 42
委托生成器:
def parent():
result = yield from child()
print("child returned:", result)
yield "parent: done"
g = parent()
print(next(g))
print(next(g))
print(next(g))
输出:
child: one
child: two
child returned: 42
parent: done
执行过程:
next(g)进入parent();- 执行
yield from child(),创建并驱动子生成器; - 子生成器产生
"child: one",直接返回给调用者; - 下一次
next(g)继续子生成器; - 子生成器产生
"child: two"; - 再次
next(g)时,子生成器执行return 42; - 子生成器结束并产生
StopIteration(42); yield from child()表达式的结果变为42;result被赋值为42;- 父生成器打印结果并产生
"parent: done"。
因此,yield from 同时传递两类信息:
- 子迭代器的中间产出值;
- 子迭代器结束时的最终返回值。
这正是普通 for ... yield 委托无法自动提供的部分。
yield from 的形式化展开
下面的代码:
result = yield from iterable
可以近似理解为以下协议循环:
iterator = iter(iterable)
try:
value = next(iterator)
except StopIteration as exc:
result = exc.value
else:
while True:
try:
sent = yield value
except GeneratorExit:
close = getattr(iterator, "close", None)
if close is not None:
close()
raise
except BaseException as exc:
throw = getattr(iterator, "throw", None)
if throw is None:
raise
try:
value = throw(type(exc), exc, exc.__traceback__)
except StopIteration as exc:
result = exc.value
break
else:
try:
if sent is None:
value = next(iterator)
else:
value = iterator.send(sent)
except StopIteration as exc:
result = exc.value
break
这个展开揭示了几个关键条件。
普通 next() 的转发
外部调用:
next(parent_generator)
最终会使子迭代器执行:
next(iterator)
send(None) 的转发
如果外部调用:
parent_generator.send(None)
委托逻辑仍然调用:
next(iterator)
因为 send(None) 在生成器协议中等价于无参数恢复。
send(value) 的转发
如果外部调用:
parent_generator.send(value)
其中 value is not None,委托逻辑会尝试调用:
iterator.send(value)
因此,子迭代器必须支持 send(),否则会出现属性错误或类型错误。语言参考也明确指出,yield from 对下层对象的 send()、throw() 支持取决于该对象是否提供相应方法。(docs.python.org)
throw() 的转发
外部注入的普通异常会优先传给子迭代器的 throw():
parent_generator.throw(ValueError("bad"))
如果子迭代器没有 throw() 方法,则该异常直接回到委托生成器。
close() 的转发
外部执行:
parent_generator.close()
如果委托生成器当前正在 yield from 子迭代器,并且子迭代器有 close(),委托逻辑会调用它,然后继续终止外层生成器。
这使 yield from 不只是值转发机制,也是完整的控制协议转发机制。PEP 380 的设计目标之一,就是让代码拆分为父生成器和子生成器后,在 next()、send()、throw() 和 close() 等行为上尽可能保持与未拆分代码一致。(peps.python.org)
yield from 与普通 for 委托的差异
比较下面两种写法:
def with_for(subgenerator):
for item in subgenerator:
yield item
def with_yield_from(subgenerator):
yield from subgenerator
如果调用者只做:
for item in generator:
...
两者可能得到相同的值。
但如果调用者通过 send() 交互:
def child():
command = yield "ready"
print("child received:", command)
yield "done"
def parent_for():
for item in child():
yield item
def parent_from():
yield from child()
对 parent_for():
g = parent_for()
print(next(g))
print(g.send("hello"))
"hello" 发送给的是 parent_for() 当前的 yield item。父生成器没有把这个值继续传给子生成器,子生成器内部的:
command = yield "ready"
得到的是 None。
而对 parent_from():
g = parent_from()
print(next(g))
print(g.send("hello"))
"hello" 会被 yield from 转发给 child(),因此子生成器会打印:
child received: hello
这就是:
for + yield
与:
yield from
的本质区别:前者只转发产出值,后者转发完整的生成器控制协议。
委托给普通可迭代对象
yield from 的目标不一定是生成器,也可以是列表、元组、文件对象或任意可迭代对象:
def combined():
yield from [1, 2]
yield from (3, 4)
yield from "ab"
print(list(combined()))
输出:
[1, 2, 3, 4, 'a', 'b']
但是普通列表迭代器通常没有:
send()
throw()
close()
因此,yield from 对它们的高级控制能力有限:
- 普通
next()可以正常转发; - 非
None的send()可能因为目标迭代器没有send()而失败; throw()不会像生成器那样进入列表迭代器内部;close()只有在下层迭代器存在close()时才会转发。
所以,“yield from 可以委托给任意可迭代对象”与“任意可迭代对象都支持完整生成器协议”是两件不同的事。
异常、关闭与委托链
考虑三层生成器:
def leaf():
try:
while True:
try:
command = yield "leaf: ready"
except ValueError:
yield "leaf: recovered"
finally:
print("leaf: cleanup")
def middle():
result = yield from leaf()
print("middle result:", result)
def root():
yield from middle()
驱动过程:
g = root()
print(next(g))
print(g.throw(ValueError("temporary")))
g.close()
输出:
leaf: ready
leaf: recovered
leaf: cleanup
路径是:
next(root)进入middle;middle委托给leaf;leaf产生"leaf: ready";root.throw(ValueError(...))沿委托链传入middle,再传入leaf;leaf捕获异常并产生"leaf: recovered";root.close()沿委托链向下传播;leaf.close()被调用;leaf的finally执行;GeneratorExit从内层向外层终止整条委托链。
委托链可以把复杂任务拆分为多个生成器,同时保留异常和关闭的传播路径。它的代价是:一旦下层生成器被多个上层对象共享,关闭其中一个委托者可能会关闭共享的下层对象。PEP 380 也明确讨论了这种共享子迭代器带来的生命周期问题。(peps.python.org)
try、finally 与生成器生命周期
yield 可以出现在 try 结构中:
def stream():
try:
yield "item"
finally:
print("stream closed")
但这并不意味着 finally 一定在生成器对象创建后立即执行,也不意味着只要调用者停止使用生成器,清理就马上发生。
下面代码中,finally 只有在生成器继续执行、显式关闭、或最终化机制介入时才会执行:
g = stream()
print("created")
# 此时 stream() 的函数体还未开始执行
print(next(g))
# 此时暂停在 yield,finally 尚未执行
g.close()
# 此时 finally 执行
如果生成器需要持有资源,应该明确设计谁负责调用 close()。否则,生成器的暂停可能长期保留资源引用。
一个常见的资源封装方式是把生成器放入上下文管理器:
from contextlib import contextmanager
@contextmanager
def managed():
print("open")
try:
yield "resource"
finally:
print("close")
with managed() as resource:
print(resource)
输出:
open
resource
close
这里的 yield 并不是普通数据流中的任意暂停点,而是上下文管理器把“进入上下文”和“离开上下文”连接起来的控制点。
生成器不是线程,也不是 asyncio 协程
生成器与协程有相似之处:
- 都能暂停;
- 都保存执行现场;
- 都能在之后恢复;
- 都可以通过某种协议与调度者交互。
但是普通生成器本身不会自动等待 I/O,也不会自动加入事件循环。
def generator():
yield "paused"
async def coroutine():
await some_operation()
普通生成器的暂停点由 yield 建立,调用者通过 next()、send() 等方法显式驱动;async def 创建的是原生协程,暂停点通常由 await 建立,并由异步运行时调度。
此外,在 async def 协程函数体中使用 yield from 是语法错误;异步生成器使用的是异步迭代协议,例如 async for、__anext__() 以及对应的异步发送和关闭机制。(docs.python.org)
因此,下面两种代码不能混为一谈:
def legacy_style():
result = yield from subgenerator()
async def modern_style():
result = await coroutine()
前者是普通生成器的委托;后者是原生协程的等待。它们在语法外观上都涉及暂停和恢复,但协议、调度器和异常终止机制不同。
一个完整的交互式生成器
下面实现一个简化的批处理生成器:
- 通过
send()接收任务; - 通过普通
yield报告结果; - 通过
throw()注入可恢复错误; - 通过
close()执行清理; - 通过
return返回最终统计结果。
def worker():
processed = 0
try:
while True:
try:
task = yield {"status": "waiting", "processed": processed}
except ValueError as exc:
yield {
"status": "error",
"message": str(exc),
"processed": processed,
}
continue
if task is None:
return {"processed": processed, "reason": "normal stop"}
if task == "stop":
return {"processed": processed, "reason": "requested stop"}
processed += 1
yield {
"status": "done",
"task": task,
"processed": processed,
}
finally:
print("worker cleanup")
驱动它:
g = worker()
# 启动,进入第一个 yield
print(next(g))
# 向当前 yield 发送任务
print(g.send("task-1"))
# 恢复到下一个等待点
print(next(g))
# 注入可恢复异常
print(g.throw(ValueError("bad task")))
# 恢复到等待点
print(next(g))
# 请求正常结束
try:
g.send("stop")
except StopIteration as exc:
print("final result:", exc.value)
输出结构类似:
{'status': 'waiting', 'processed': 0}
{'status': 'done', 'task': 'task-1', 'processed': 1}
{'status': 'waiting', 'processed': 1}
{'status': 'error', 'message': 'bad task', 'processed': 1}
{'status': 'waiting', 'processed': 1}
worker cleanup
final result: {'processed': 1, 'reason': 'requested stop'}
这个例子中的关键点是:
- 生成器启动后先停在“等待任务”的
yield; send("task-1")让该yield表达式的结果变为"task-1";- 处理任务后,生成器在另一个
yield处暂停并返回任务结果; - 下一次
next()使它回到等待位置; throw(ValueError(...))在等待任务的yield处触发异常;- 异常被内部处理后,生成器产生错误报告;
send("stop")使生成器执行return;return的值通过StopIteration.value交给调用者;- 无论正常停止还是异常关闭,
finally都承担清理责任。
这个协议适合表达“由外部调度者驱动的状态机”。但它并不自动提供消息队列、线程安全、背压、超时或取消语义。如果多个线程同时操作同一个生成器,需要在生成器外部建立同步和生命周期管理。
诊断生成器问题的方法
检查是否为生成器函数
import inspect
def source():
yield 1
print(inspect.isgeneratorfunction(source))
输出:
True
检查是否为生成器对象
g = source()
print(inspect.isgenerator(g))
输出:
True
检查当前状态
print(inspect.getgeneratorstate(g))
next(g)
print(inspect.getgeneratorstate(g))
检查是否正在委托
生成器对象有一个常见的内省属性:
g.gi_yieldfrom
当生成器当前没有委托给下层对象时,通常为 None;执行到 yield from 并暂停在下层迭代器时,可以观察到对应的委托对象。gi_yieldfrom 是生成器对象提供的内省属性,Python 文档记载它自 Python 3.5 起可用。(docs.python.org)
常见失败与定位方向
1. 忘记启动就调用 send(value)
TypeError: can't send non-None value to a just-started generator
修复方式:
next(g)
g.send(value)
2. 把 send() 的返回值当成最终返回值
send() 返回的是生成器下一次 yield 的值;生成器的 return 值要从 StopIteration.value 读取。
3. 在 GeneratorExit 后继续 yield
这会触发:
RuntimeError: generator ignored GeneratorExit
修复方式是在清理后结束生成器,不要继续产生值。
4. 在生成器内部直接抛出 StopIteration
现代 Python 会将其转换为 RuntimeError。使用 return value 表示正常结束。
5. 以为 yield from 只是语法缩写
对于简单的 next() 遍历,它类似于 for ...: yield ...;对于 send()、throw()、close() 和最终返回值,它是更完整的协议委托。
6. 重复驱动同一个生成器
生成器不是可重入对象。生成器执行期间再次驱动它,会触发“generator already executing”类错误。应当由一个明确的调度者顺序驱动,或为每个独立任务创建不同的生成器实例。
规范保证、实现行为与工程取舍
规范保证
Python 3.14 语言层面保证:
- 使用
yield的函数是生成器函数; - 生成器对象在
yield处暂停并保存执行状态; send()将值作为当前yield表达式的结果;throw()在暂停点引发异常;close()通过GeneratorExit请求终止;yield from转发下层迭代器的值和部分控制操作;- 子迭代器的
StopIteration.value成为yield from表达式的结果。(docs.python.org)
常见实现行为
在 CPython 中:
- 生成器通常持有可观察的 Python 栈帧;
- 引用计数经常使对象失去最后引用后较快触发最终化;
gi_frame、gi_yieldfrom等属性可用于诊断。
但具体栈帧暴露方式、垃圾回收时机和对象销毁时机不应被当作所有 Python 实现都相同的语义保证。inspect.getgeneratorlocals() 文档也明确指出,它依赖 Python 栈帧暴露能力,在某些实现中可能只能返回空字典。(docs.python.org)
工程取舍
如果需求只是惰性地产生一系列值,普通 yield 通常足够:
def read_rows():
for row in source:
yield transform(row)
如果需求包含“向暂停计算发送结果”,才需要:
result = yield request
如果需求包含“从外部取消或通知异常”,可以考虑:
try:
command = yield state
except Cancelled:
...
如果需要把复杂控制流拆分到多个生成器,同时保留值、输入、异常和关闭传播,应使用:
result = yield from subgenerator()
如果需求是现代异步 I/O、事件循环调度或任务取消,优先使用 async def、await 和 asyncio 的原生协程体系,而不是自行用普通生成器模拟调度器。普通生成器仍然适合表达同步的惰性计算、解析器状态机、数据管线和可控的协作式控制流,但它不会自动提供异步运行时能力。
生成器真正的核心不是“少创建一个列表”,而是建立了一个可暂停的执行协议:
next() —— 继续执行,不传入业务值
send(value) —— 继续执行,并让 yield 表达式得到 value
throw(exc) —— 在暂停点引发异常
close() —— 请求以 GeneratorExit 终止
yield from —— 将这些控制操作委托给下层迭代器
掌握这组协议后,yield 不再只是一个惰性循环语法,而是一种完整的双向控制流机制。
系列导航与关联阅读
- 系列入口:Python 完整学习路线:从语言模型、并发到 Web、数据、AI 与生产交付
- 上一篇:Python 可迭代对象与迭代器:协议、惰性、耗尽和组合
- 下一篇:Python 推导式:列表、集合、字典、生成器与作用域边界
- 延伸:Python asyncio 完整基础:事件循环、协程、Future 与调度
官方资料
本文依据 Python 官方文档、相关 PEP 与生态项目官方文档重新梳理;正文、示例与工程清单由 WR BLOG 编写。

评论
0 条讨论