生成器的本质是实现了__iter__和__next__的状态机对象,通过挂起/恢复帧对象保存上下文,而非线程栈;yield暂停执行并返回值,下次从原位置继续,局部变量保持不变。

generator 的本质不是语法糖,而是实现了 iter 和 next 的状态机对象。
直接调用 next() 或用 for 循环驱动时,它靠保存局部变量和执行位置(yield 点)来维持上下文——这和线程栈无关,也不依赖解释器额外调度。
为什么 yield 后函数不退出?
因为每次遇到 yield,Python 解释器会把当前帧(frame)挂起,把控制权交还给调用方,并把 yield 表达式的值返回。下一次调用 __next__() 时,从上次暂停的位置继续执行,局部变量全数保留。
-
return在生成器中等价于raise StopIteration,但不能带非None值(Python 3.3+ 允许return value,该值会成为StopIteration.value) - 函数体里没有
yield,哪怕写了return,也不会变成生成器——必须至少有一个yield(哪怕在未执行的if False:分支里) - 生成器函数被调用时,**不执行函数体**,只返回一个
generator对象
send() 和 throw() 怎么打破单向数据流?
生成器不只是“往外吐值”,还能接收外部传入的数据或异常,从而实现协程式交互。关键在于:第一次调用 send(None) 或 next() 才启动生成器;之后才能用 send(value) 把值送进上次 yield 的位置(即 value 成为 yield 表达式的返回值)。
立即学习“Python免费学习笔记(深入)”;
def echo():
while True:
received = yield
print(f"Got: {received}")
g = echo()
next(g) # 启动,停在第一个 yield
g.send("hello") # 输出 Got: hello
g.send(42) # 输出 Got: 42
- 首次调用必须是
next(g)或g.send(None),否则报TypeError: can't send non-None value to a just-started generator -
throw()会在暂停处注入异常,常用于清理资源(如配合try/finally) -
close()会触发GeneratorExit异常,且禁止再调用send()或next()
生成器表达式 vs 列表推导式:内存与延迟的关键区别
(x*2 for x in range(1000000)) 不会立刻计算全部元素,而 [x*2 for x in range(1000000)] 会一次性分配百万级整数对象内存。
- 生成器表达式返回
;列表推导式返回at 0x...> list - 生成器只能遍历一次,再次
for就无声结束;列表可反复迭代 - 生成器表达式不能切片、索引、
len()—— 它没有长度概念,除非你手动计数或转成list(那就失去意义了) - 嵌套生成器要注意:外层生成器若引用内层生成器对象,但没消费完,可能造成意外延迟释放(比如文件句柄没及时关)
常见误用与调试盲区
最隐蔽的问题不是语法错,而是逻辑生命周期错位:生成器已结束却还在调用 next(),抛出 StopIteration;或在 finally 中试图访问已被清空的局部变量。
- 捕获
StopIteration是正常操作,但不要忽略其value属性(Python 3.3+),尤其用return expr时 - 用
itertools.islice(gen, n)替代list(gen)[:n],避免提前耗尽生成器 -
yield from subgen不是简单展开,它会将子生成器的send()/throw()/close()透传,且自动处理StopIteration的传播 - 调试时别用
print(g)看状态——要查g.gi_running(是否正在运行)、g.gi_frame.f_lasti(字节码偏移)这类底层属性,实际开发中几乎不用,但理解它们能避开“为什么断点没进生成器体”的困惑
生成器的复杂性不在写法,而在它把执行流拆成了多个时间片段。一旦你开始用 send() 或嵌套 yield from,就必须明确每个片段的输入来源、输出去向、异常边界——这些不会报错,但会让程序在某个深夜突然卡住或跳过关键步骤。










