Python 的 yield from 是什么?和 for 循环里 yield 有什么区别?
简化版
**yield from iterable 是「生成器委托」——把一个生成器/可迭代对象的所有值「转发」给外层生成器,等价于 for item in iterable: yield item,但它不只是语法糖:还能自动「打通」子生成器和调用方之间的双向通道(转发 send/throw、接收子生成器的返回值)。**基本用法:yield from sub_gen() 把 sub_gen 产出的所有值逐个 yield 出去,写起来比 for-yield 简洁。它比 for-yield 多做的事:① 转发子生成器的返回值——value = yield from sub() 能拿到子生成器 return 的值(for-yield 拿不到);② 打通双向通信——外层收到的 send(x)、throw(e) 会自动转发给子生成器(协程场景关键);③ 自动处理子生成器的关闭/异常。最常见的简单用法是「扁平化/委托」:yield from [1,2,3]、yield from range(n)、递归展开嵌套结构(yield from flatten(sub))。核心记忆:yield from 把子可迭代对象的值全部转发给外层(≈ for item in it: yield item),但额外转发 send/throw、接收子生成器返回值,是委托子生成器的标准写法。
详细版
yield from vs for-yield:
| 能力 | for x in it: yield x | yield from it |
|---|---|---|
| 转发所有值 | ✅ | ✅ |
| 接收子生成器 return 值 | ❌ | ✅ v = yield from sub() |
| 转发 send/throw 给子 | ❌ | ✅(协程双向通道) |
| 简洁 | 两行 | 一行 |
# 基本:转发可迭代对象的所有值
def gen():
yield from [1, 2, 3] # 等价 for x in [1,2,3]: yield x
yield from range(4, 6) # 4, 5
list(gen()) # [1, 2, 3, 4, 5]
# 委托子生成器(组合多个生成器)
def sub1():
yield 'a'; yield 'b'
def sub2():
yield 'c'; yield 'd'
def combined():
yield from sub1()
yield from sub2()
list(combined()) # ['a', 'b', 'c', 'd']
# 递归扁平化嵌套列表(yield from 的经典用途)
def flatten(nested):
for item in nested:
if isinstance(item, list):
yield from flatten(item) # 递归委托
else:
yield item
list(flatten([1, [2, [3, 4], 5], 6])) # [1, 2, 3, 4, 5, 6]
# 接收子生成器的 return 值(for-yield 做不到)
def sub():
yield 1
yield 2
return "done" # 子生成器的返回值
def outer():
result = yield from sub() # result 拿到 "done"
print(f"子生成器返回: {result}")
yield result
list(outer()) # 打印 "子生成器返回: done",产出 [1, 2, 'done']
# 对比 for-yield:拿不到 return 值
def outer_forloop():
for x in sub(): # sub 的 return "done" 被丢弃
yield x
⚠️ 理解要点:
yield from在最简单的场景里就是for x in it: yield x的简写(把子可迭代对象的值全部转发出去),但它真正的设计目的(PEP 380)是「让一个生成器能把一部分工作『委托』给子生成器,同时保持双向通道畅通」——这在把生成器当「协程」用时至关重要。当生成器只用来「产出值」时,yield from和 for-yield 几乎等价(前者更简洁、更快);但当生成器被当协程(用send()传值进去、用throw()抛异常进去、用return返回结果)时,yield from会自动把外层收到的send/throw转发给子生成器、把子生成器return的值作为yield from表达式的值——这些 for-yield 都做不到,手写转发极其繁琐(PEP 380 的动机就是「消除手写委托的样板代码」)。日常最常用的还是简单场景:扁平化嵌套结构、组合/串联多个生成器、把可迭代对象的值批量 yield。注意yield from后面可以跟任何可迭代对象(列表、range、生成器、字符串),不一定是生成器。
完整版教学
一、yield from 是什么
先理解基本语义:
yield from iterable:把 iterable 的所有值转发给外层
最简单理解 ≈ for item in iterable: yield item
基本用法:
def gen():
yield from [1, 2, 3] # 逐个产出 1, 2, 3
yield from range(4, 6) # 逐个产出 4, 5
→ 把子可迭代对象"展开"到外层生成器的输出流里
后面可以跟任何可迭代对象:
yield from [1,2,3] # 列表
yield from range(5) # range
yield from "abc" # 字符串(产出 'a','b','c')
yield from another_gen() # 生成器
yield from {1,2,3} # 集合
对比手写 for-yield:
# 这两个在"产出值"上等价:
yield from sub()
for x in sub(): yield x
→ yield from 更简洁(一行)
为什么叫"委托(delegation)":
外层生成器把"产出值的工作"委托给子生成器
外层暂时"退居幕后",子生成器直接和调用方交互
子生成器耗尽后,控制权回到外层
引入版本:
Python 3.3(PEP 380)引入 yield from
动机:简化"生成器委托",尤其是协程场景
所以 yield from 把子可迭代对象所有值转发给外层(≈for x in it:yield x),叫委托
yield from iterable 把 iterable 的所有值转发给外层——最简单理解 ≈ for item in iterable: yield item。基本用法:yield from [1,2,3] 逐个产出、把子可迭代对象「展开」到外层输出流。后面可以跟任何可迭代对象:列表、range、字符串(产出字符)、生成器、集合。对比手写 for-yield:yield from sub() 和 for x in sub(): yield x 在产出值上等价(yield from 更简洁)。为什么叫「委托(delegation)」:外层生成器把「产出值的工作」委托给子生成器(外层暂时退居幕后、子生成器直接和调用方交互、子耗尽后控制权回外层)。引入版本:Python 3.3(PEP 380),动机是简化生成器委托(尤其协程)。理解「yield from 把子可迭代对象所有值转发给外层(≈for x in it:yield x);后面可跟任何可迭代对象;叫委托(外层把产出工作委托给子生成器);Python 3.3/PEP 380 引入」,就理解了 yield from 是什么。
二、比 for-yield 多做的事
理解 yield from 的额外能力:
yield from 不只是 for-yield 的简写,它还做:
① 转发子生成器的 return 值:
def sub():
yield 1
return "result" # 子生成器返回
def outer():
value = yield from sub() # value = "result"
# for-yield 拿不到 return 值(会被丢弃)
→ 子生成器 return 的值成为 yield from 表达式的值
② 转发 send() 给子生成器(双向通道):
外层收到的 send(x) → 自动转发给正在委托的子生成器
def sub():
x = yield # 接收 send 的值
yield x * 2
def outer():
yield from sub()
g = outer(); next(g)
g.send(10) # 10 被转发给 sub 的 x → 产出 20
③ 转发 throw() 给子生成器:
外层 throw(异常) → 转发给子生成器处理
④ 自动处理子生成器的 close/StopIteration:
子生成器结束(StopIteration)→ 自动回到外层
外层 close → 子生成器也被正确关闭
手写这些转发极其繁琐:
PEP 380 前,要正确委托一个协程子生成器
得手写一大段处理 send/throw/return/close 的样板代码
→ yield from 一行搞定所有转发
所以 yield from 额外:转发子生成器 return 值+转发 send/throw+处理 close(for-yield 做不到)
yield from 不只是 for-yield 的简写,它还做:① 转发子生成器的 return 值(value = yield from sub(),子生成器 return "result" 的值成为 yield from 表达式的值、for-yield 拿不到会被丢弃);② 转发 send() 给子生成器(双向通道)(外层收到的 send(x) 自动转发给正在委托的子生成器);③ 转发 throw() 给子生成器(外层 throw(异常) 转发给子处理);④ 自动处理子生成器的 close/StopIteration(子结束自动回外层、外层 close 时子也被正确关闭)。手写这些转发极其繁琐:PEP 380 前要正确委托一个协程子生成器得手写一大段处理 send/throw/return/close 的样板、yield from 一行搞定。理解「yield from 额外:①转发子生成器 return 值(value=yield from sub())②转发 send 给子(双向通道)③转发 throw④处理 close/StopIteration;for-yield 都做不到;手写委托极繁琐、yield from 一行搞定」,就掌握了额外能力。
三、经典用途:扁平化与组合
理解日常最常用的场景:
日常最常用(简单委托,不涉及协程):
① 扁平化嵌套结构(递归委托):
def flatten(nested):
for item in nested:
if isinstance(item, (list, tuple)):
yield from flatten(item) # 递归展开
else:
yield item
list(flatten([1, [2, [3, 4]], 5])) → [1,2,3,4,5]
→ yield from 让递归生成器写起来自然
② 组合/串联多个生成器:
def chain_gens():
yield from gen_a()
yield from gen_b()
yield from gen_c()
→ 把多个生成器的输出串成一个流
(itertools.chain 也能做,但 yield from 更灵活可插逻辑)
③ 批量 yield 一个可迭代对象:
def with_header():
yield "HEADER"
yield from data_rows() # 批量产出所有行
yield "FOOTER"
④ 转发子迭代 + 加工:
def process():
yield from map(transform, source())
# 或先产出一部分自己的,再委托
yield "start"
yield from items()
⑤ 树遍历(生成器版):
def inorder(node):
if node:
yield from inorder(node.left)
yield node.val
yield from inorder(node.right)
→ 中序遍历,惰性产出
对比 itertools.chain:
yield from a; yield from b ≈ itertools.chain(a, b)
→ chain 更简洁(纯串联),yield from 能插入自己的逻辑
所以经典用途:扁平化(递归)、组合多生成器、批量 yield、树遍历(生成器版)
日常最常用(简单委托,不涉及协程):① 扁平化嵌套结构(递归委托)(yield from flatten(item) 递归展开、让递归生成器写起来自然);② 组合/串联多个生成器(yield from gen_a(); yield from gen_b() 把多个生成器输出串成一个流、itertools.chain 也能做但 yield from 更灵活可插逻辑);③ 批量 yield 一个可迭代对象(yield "HEADER"; yield from data_rows(); yield "FOOTER");④ 转发子迭代 + 加工;⑤ 树遍历(生成器版)(yield from inorder(node.left); yield node.val; yield from inorder(node.right) 中序遍历惰性产出)。对比 itertools.chain:yield from a; yield from b ≈ chain(a, b)(chain 更简洁纯串联、yield from 能插入自己逻辑)。理解「经典用途:①扁平化嵌套(递归 yield from)②组合串联多生成器③批量 yield(带 header/footer)④转发+加工⑤树遍历生成器版;vs itertools.chain(chain 纯串联、yield from 可插逻辑)」,就掌握了经典用途。
四、协程场景(进阶)
理解 yield from 在协程里的作用:
生成器当"协程"用(yield from 的设计初衷):
生成器可以是双向的:
yield 产出值给调用方
send(x) 把值传进生成器(yield 表达式的返回值)
→ 生成器变成"能接收也能产出"的协程
协程委托的问题(PEP 380 前):
外层协程想把一部分工作交给子协程
但要正确转发 send/throw/return/close 极其麻烦
→ 手写一大段委托样板代码
yield from 解决:一行完成协程委托
def averager(): # 子协程:累计平均
total = 0; count = 0
while True:
x = yield # 接收 send 的值
if x is None: break
total += x; count += 1
return total / count # 返回结果
def manager():
result = yield from averager() # 委托 + 拿返回值
print(f"平均值: {result}")
→ send 的值自动转发给 averager
→ averager 的 return 值成为 yield from 的值
历史地位:
yield from(PEP 380, 3.3)是"协程"发展的关键一步
→ 后来 async/await(3.5, PEP 492)取代了用生成器做协程
→ 现在异步编程用 async/await,yield from 主要用于生成器委托
(async def 里用 await,不用 yield from 做异步)
所以协程场景:yield from 一行完成协程委托(转发 send/throw+拿 return 值),是 async/await 前身
生成器当「协程」用(yield from 的设计初衷):生成器可以是双向的(yield 产出值、send(x) 把值传进生成器成为 yield 表达式的返回值、生成器变成能接收也能产出的协程)。协程委托的问题(PEP 380 前):外层协程想把一部分工作交给子协程、但要正确转发 send/throw/return/close 极其麻烦(手写一大段委托样板)。yield from 解决:一行完成协程委托(result = yield from averager()——send 的值自动转发给 averager、averager 的 return 值成为 yield from 的值)。历史地位:yield from(PEP 380、3.3)是协程发展的关键一步、后来 async/await(3.5、PEP 492)取代了用生成器做协程(现在异步编程用 async/await、async def 里用 await 不用 yield from 做异步),yield from 现在主要用于生成器委托。理解「协程场景:生成器双向(yield 产出、send 传入);yield from 一行完成协程委托(转发 send/throw+拿 return 值);是 async/await(3.5)的前身、现在异步用 async/await、yield from 主要用于生成器委托」,就掌握了协程场景。
五、注意事项与陷阱
理解 yield from 的注意点:
注意事项:
① yield from 后面可以是任何可迭代对象:
yield from [1,2,3] # 列表
yield from "abc" # 字符串 → 'a','b','c'
yield from range(5)
→ 不限于生成器
② 空可迭代对象:yield from [] 什么都不产出(正常)
③ return 值只在"委托给生成器"时有意义:
value = yield from gen() # gen 的 return 值
value = yield from [1,2,3] # 列表没有 return,value 是 None
→ 只有生成器能 return 值
④ 别和普通 yield 混淆:
yield gen() # 产出"生成器对象本身"(一个值)
yield from gen() # 产出生成器的"所有值"(展开)
→ 差一个 from,语义完全不同!
⑤ 异常传播:
子生成器内的异常会传播到外层
外层的 throw 会转发给子生成器
⑥ 性能:
yield from 比手写 for-yield 略快(C 层优化转发)
⑦ 不要在 async def 里用 yield from:
async def 用 await(不是 yield from)
→ yield from 是生成器委托、await 是异步等待,别混
常见错误:
yield gen() 想展开但只产出了生成器对象
→ 要展开用 yield from gen()
所以注意:后面可跟任何可迭代、return 值只对生成器有意义、别和 yield gen()混淆
注意事项:① yield from 后面可以是任何可迭代对象(列表、字符串、range,不限于生成器);② 空可迭代对象(yield from [] 什么都不产出);③ return 值只在委托给生成器时有意义(yield from [1,2,3] 列表没有 return、value 是 None);④ 别和普通 yield 混淆(yield gen() 产出生成器对象本身、yield from gen() 产出生成器的所有值展开,差一个 from 语义完全不同);⑤ 异常传播(子生成器异常传到外层、外层 throw 转发给子);⑥ 性能(yield from 比手写 for-yield 略快);⑦ 不要在 async def 里用 yield from(async def 用 await、yield from 是生成器委托、await 是异步等待)。理解「注意:①后面可跟任何可迭代②空的不产出③return 值只对生成器有意义④别和 yield gen()混淆(差一个 from、语义全不同)⑤异常传播⑥略快⑦async def 用 await 不用 yield from」,就掌握了注意事项。
六、总结与实践
总结 yield from:
核心:
yield from iterable:把可迭代对象的所有值转发给外层
最简单 ≈ for x in it: yield x(但不止于此)
比 for-yield 多做:
① 接收子生成器的 return 值(value = yield from gen())
② 转发 send/throw 给子生成器(协程双向通道)
③ 自动处理子生成器的 close/异常
日常最常用(简单委托):
扁平化嵌套(递归 yield from)
组合/串联多个生成器
批量 yield 可迭代对象
树遍历(生成器版)
进阶(协程):
yield from 一行完成协程委托(是 async/await 前身)
现在异步用 async/await,yield from 主要用于生成器委托
易错:
yield gen()(产出生成器对象) vs yield from gen()(展开)
差一个 from,语义完全不同
实践建议:
批量转发可迭代对象 → yield from(简洁、快)
纯串联生成器 → itertools.chain 也可
异步编程 → async/await(不是 yield from)
核心总结:
yield from 委托子可迭代对象(转发所有值)
额外转发 send/throw、接收 return 值
日常用于扁平化/组合/树遍历
所以 yield from 委托子可迭代(转发所有值+send/throw+return),日常扁平化/组合
核心:yield from iterable 把可迭代对象的所有值转发给外层(最简单 ≈ for x in it: yield x 但不止于此)。比 for-yield 多做:① 接收子生成器的 return 值、② 转发 send/throw 给子(协程双向通道)、③ 自动处理 close/异常。日常最常用:扁平化嵌套、组合串联多生成器、批量 yield、树遍历。进阶(协程):一行完成协程委托(是 async/await 前身、现在异步用 async/await)。易错:yield gen()(产出生成器对象)vs yield from gen()(展开)差一个 from。理解「yield from 委托子可迭代(转发所有值);额外转发 send/throw+接收 return 值;日常扁平化/组合/树遍历;易错 yield gen() vs yield from gen();异步用 async/await」,就掌握了总结与实践。
记忆钩子:「yield from iterable 是『生成器委托』——把子生成器/可迭代对象的所有值转发给外层生成器,最简单理解≈for item in iterable:yield item(把子可迭代对象的值全部展开到外层输出流);★但它不只是语法糖(PEP 380/Python 3.3),比 for-yield 多做三件事:①接收子生成器的 return 值(value=yield from sub(),for-yield 会丢弃 return 值)②转发 send()/throw()给正在委托的子生成器(打通协程双向通道)③自动处理子生成器的 close/StopIteration;★日常最常用是简单委托:扁平化嵌套结构(递归 yield from flatten(item))、组合串联多个生成器(yield from a;yield from b≈itertools.chain 但可插逻辑)、批量 yield 可迭代对象、树遍历生成器版(yield from inorder(left);yield val;yield from inorder(right));★易错:yield gen()产出『生成器对象本身』(一个值)、yield from gen()产出生成器的『所有值』(展开),差一个 from 语义完全不同;后面可跟任何可迭代对象(列表/range/字符串/生成器);★协程场景是它的设计初衷(是 async/await 的前身),但现在异步编程用 async/await(async def 里用 await 不用 yield from),yield from 主要用于生成器委托」。
七、常见误区与追问
- 误区:yield from it 和 for x in it: yield x 完全等价。 在「只产出值」时等价(也更简洁、略快),但
yield from还多做三件 for-yield 做不到的事:① 能接收子生成器return的值(value = yield from gen(),for-yield 会丢弃 return 值);② 把外层收到的send()/throw()自动转发给正在委托的子生成器(打通协程双向通道);③ 自动处理子生成器的关闭和异常;所以在把生成器当协程用时两者有本质区别。 - 误区:yield gen() 和 yield from gen() 一样。 差一个 from、语义完全不同——
yield gen()是把「生成器对象本身」作为一个值产出(外层只 yield 出一个生成器对象),yield from gen()是把「生成器产出的所有值」逐个转发出去(展开);比如def f(): yield gen()迭代得到[<generator object>],def f(): yield from gen()迭代得到生成器的实际内容;想「展开」子生成器一定要用yield from。 - 误区:yield from 后面必须是生成器。 不是——
yield from后面可以跟任何可迭代对象:列表yield from [1,2,3]、rangeyield from range(5)、字符串yield from "abc"(产出 ‘a’,‘b’,‘c’)、集合、其他生成器都行;只不过「接收 return 值」这个特性只有子对象是生成器时才有意义(列表等没有 return、value = yield from [1,2,3]里 value 是 None)。 - 误区:在 async def 里用 yield from 做异步等待。 不对——
yield from是「生成器委托」、用在普通生成器函数里;异步函数(async def)里要用await来等待协程/可等待对象;虽然历史上 asyncio 早期确实用过yield from配合生成器协程(@asyncio.coroutine+yield from),但那是 Python 3.5 之前的旧写法,现在一律用async def+await;yield from和await是两套东西,别在异步代码里混用。 - 追问:yield from 相比手写 for-yield,多了哪些能力,为什么需要它? 多了三个关键能力:① 接收子生成器的返回值——
value = yield from sub()能拿到 sub 里return x的 x(for-yield 里子生成器的 return 值会被直接丢弃);② 双向通道转发——外层生成器收到的send(值)、throw(异常)、close()会自动转发给当前正在委托的子生成器,让调用方能「穿透」外层直接和子生成器交互;③ 自动处理子生成器的 StopIteration/异常/关闭;PEP 380 引入它的动机就是:在把生成器当协程用时,手写这些转发逻辑(正确处理 send/throw/return/close 的各种情况)需要一大段繁琐易错的样板代码,yield from把这些全部封装成一行,让「生成器委托子生成器」变得简单可靠。 - 追问:yield from 在实际代码里最常见的用途是什么? 最常见的是「简单委托」(不涉及协程的双向通信):① 递归扁平化嵌套结构——
def flatten(nested): for x in nested: yield from flatten(x) if isinstance(x, list) else [x],让递归生成器写起来自然;② 组合/串联多个生成器成一个输出流——def combined(): yield from gen_a(); yield from gen_b()(类似 itertools.chain,但可以在中间插入自己的逻辑或额外的 yield);③ 批量产出一个可迭代对象的所有值——yield "开始"; yield from data(); yield "结束";④ 生成器版的树遍历——中序遍历yield from inorder(left); yield val; yield from inorder(right),惰性地按顺序产出节点;这些场景里 yield from 主要图的是「简洁 + 略快 + 递归自然」。 - 追问:yield from 和 async/await 是什么关系? yield from(PEP 380,Python 3.3)是「生成器委托」机制,也是 Python 协程发展史上的关键一步——在 async/await 出现之前,asyncio 就是用「生成器 + yield from」来实现协程和协程委托的(
@asyncio.coroutine装饰的生成器里用yield from等待另一个协程);后来 Python 3.5(PEP 492)引入了专门的async def和await语法,把「异步协程」和「普通生成器」在语法上彻底分开,await承担了当年yield from在异步场景的角色、语义更清晰;所以可以说 yield from 是 async/await 的「前身/思想来源」;现在的分工是:异步编程用async def+await,而yield from回归到它最基础的用途——普通生成器之间的委托(扁平化、组合、树遍历等),两者不要混用。
八、加强记忆
yield from iterable 是「生成器委托」——把子生成器/可迭代对象的所有值转发给外层生成器,最简单理解 ≈ for item in iterable: yield item(把子可迭代对象的值全部展开到外层输出流)。但它不只是语法糖(PEP 380/Python 3.3),比 for-yield 多做三件事:① 接收子生成器的 return 值(value = yield from sub(),for-yield 会丢弃 return 值)、② 转发 send()/throw() 给正在委托的子生成器(打通协程双向通道)、③ 自动处理子生成器的 close/StopIteration。日常最常用是简单委托:扁平化嵌套结构(递归 yield from flatten(item))、组合串联多个生成器(yield from a; yield from b ≈ itertools.chain 但可插逻辑)、批量 yield 可迭代对象、树遍历生成器版。易错:yield gen() 产出「生成器对象本身」(一个值)、yield from gen() 产出生成器的「所有值」(展开),差一个 from 语义完全不同;后面可跟任何可迭代对象。协程场景是它的设计初衷(是 async/await 的前身),但现在异步编程用 async/await(async def 里用 await 不用 yield from),yield from 主要用于生成器委托。一句话「yield from 委托子可迭代对象、转发其所有值(≈for x in it:yield x);额外接收子生成器 return 值、转发 send/throw、处理 close;日常用于扁平化/组合生成器/树遍历;别和 yield gen()混淆(差一个 from);异步用 async/await 不是 yield from」。