yield from 是 python 中用于生成器委托的关键字,可惰性扁平化任意嵌套可迭代对象(非字符串),自动处理子生成器的 yield、send、throw 和返回值,区别于普通 yield 的单值产出和手动 for 循环的局限性。

Python 中没有 yield* 语法,这是 JavaScript 的写法;Python 对应的是 yield from。所以问题实质是:如何用 yield from 正确处理多层嵌套的可迭代对象(如嵌套列表、生成器、元组等),实现惰性、全量、逐个产出的扁平数据流。
区分 yield from 和普通 yield 的关键作用
yield from 不是语法糖的“快捷写法”,而是生成器委托机制——它把子生成器的整个控制流交还给调用方,自动处理 StopIteration、send()、throw() 等交互逻辑。普通 for + yield 手动迭代无法做到这点,尤其在协程或需中断恢复的场景中会出错。
- 适合用
yield from的:嵌套生成器、递归生成器、需要保持迭代状态的流式结构 - 不适合用
yield from的:单个非可迭代值(如整数、None)、字符串(除非你真想拆成字符) - 常见误用:对
int或str直接yield from→ 触发TypeError: 'int' object is not iterable
安全扁平化任意嵌套可迭代对象的生成器写法
核心是做类型判断:只对“可迭代但非字符串”的对象递归委托,其余直接产出。
from collections.abc import Iterable <p>def flatten_stream(nested): for item in nested: if isinstance(item, Iterable) and not isinstance(item, (str, bytes)): yield from flatten_stream(item) else: yield item </p>
- 这个函数返回的是生成器对象,不立即执行,内存占用恒定
- 支持混合结构:
[1, [2, (3, [4])], "hello", [5.0]]→ 产出1, 2, 3, 4, "hello", 5.0(字符串整体保留) - 可用于 JSON 数组解析后的嵌套结果、树节点遍历、API 分页响应流等真实数据流场景
配合内置可迭代对象的实际用法示例
比如你从 API 流式读取多个批次的嵌套响应:
# 模拟分页返回的嵌套数据流 def fetch_page_1(): yield [1, [2, 3]] def fetch_page_2(): yield [[4, [5]], 6] def fetch_page_3(): yield [7, 8, [9, [10]]] <h1>统一扁平化所有页面的数据</h1><p>def all_flattened(): for page_gen in [fetch_page_1(), fetch_page_2(), fetch_page_3()]: for batch in page_gen: yield from flatten_stream(batch)</p><h1>全量拿回(转 list)或逐个消费(for 循环)</h1><p>result = list(all_flattened()) # [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] </p>
- 全程无中间列表构建,适合大数据流或内存受限环境
-
flatten_stream可复用,不耦合具体数据源类型(list/tuple/生成器/自定义迭代器) - 若需提前终止(如找到第 N 个有效值就停),可用
itertools.islice或手动 break,不影响底层生成器状态
不推荐但常被误用的替代方案对比
itertools.chain.from_iterable 只展开一层,且要求输入中每个元素本身必须是可迭代对象;遇到 [1, [2, 3]] 就失败。
sum(nested, []) 或 functools.reduce(operator.iadd, nested, []) 会强制构建完整列表,失去惰性,且对深层嵌套无效、不可控。
列表推导式 [x for sub in nested for x in sub] 同样仅限二维,且无法跳过非可迭代项(报错)或保留字符串原语义。
这些方法都做不到“全量拿回多层嵌套流的扁平数据切面”——它们要么深度固定,要么破坏流式特性,要么类型鲁棒性差。











