【Python AI教程】(三)生成器与迭代器:流式AI数据处理的基石
当你调用 ChatGPT 或 Claude 时,为什么回复是逐字出现而不是一次性返回?这背后正是流式处理(Streaming) 在发挥作用。而在 Python 中,生成器和迭代器是实现这一能力的基石。
一、迭代器协议:__iter__ 和 __next__
1.1 协议的两半
Python 的迭代体系建立在两个方法之上:
1 | class Range: |
1.2 迭代器 vs 可迭代对象
| 类型 | __iter__ | __next__ | 举例 |
|---|---|---|---|
| 可迭代对象 | ✅ 返回迭代器 | ❌ 不需要 | list, str, dict, set |
| 迭代器 | ✅ 返回自身 | ✅ 必须实现 | Range, map, filter |
1 | nums = [1, 2, 3] # list 是可迭代对象 |
二、生成器函数:yield 的状态保存魔法
2.1 生成器即状态机
生成器(Generator) 是用 yield 关键字定义的特殊函数,每次 yield 会暂停执行并保存当前状态:
1 | def count_up(n): |
2.2 生成器状态机详解
stateDiagram-v2
[*] --> Created: def count_up(5)
Created --> Ready: 首次 next()
Ready --> Suspended: yield current
Suspended --> Ready: next() 恢复
Ready --> Exhausted: current >= n
Exhausted --> [*]: StopIteration
note right of Suspended: "保存:current 的值<br/>寄存器状态<br/>异常处理上下文"2.3 生成器的控制:throw() 和 close()
生成器提供了与外部交互的能力:
1 | def infinite_gen(): |
三、无限生成器与 itertools
3.1 斐波那契:永不枯竭的序列
1 | import itertools |
3.2 常用 itertools 函数
| 函数 | 作用 | 示例 |
|---|---|---|
chain(*iterables) | 连接多个序列 | chain([1,2], [3,4]) → 1,2,3,4 |
islice(iterable, stop) | 切片迭代器 | islice(fib(), 10) |
cycle(iterable) | 无限循环 | cycle([1,2]) → 1,2,1,2,... |
accumulate(iterable) | 累积计算 | accumulate([1,2,3]) → 1,3,6 |
groupby(iterable) | 分组 | groupby(['a','a','b']) |
1 | # groupby 示例 |
四、生成器表达式
生成器表达式是列表推导式的惰性版本:
1 | # 列表推导式:立即计算,返回完整列表 |
五、AI 实战:流式 LLM 输出
5.1 模拟流式 Token 生成
在真实 AI 应用中,LLM 的输出是通过 Server-Sent Events(SSE 或 WebSocket 逐 token 推送的:
1 | from typing import Generator |
5.2 数据处理流水线
在大规模 AI 数据处理中,生成器链可以构建高效的流水线:
1 | import itertools |
5.3 流水线执行流程
flowchart LR
subgraph "数据源"
A["URLs 列表"]
end
subgraph "Stage 1: fetch_batches"
B["批次1 [0-4]"]
C["批次2 [5-9]"]
D["批次3 [10-14]"]
end
subgraph "Stage 2: process_batch"
E["处理后的批次1"]
F["处理后的批次2"]
G["处理后的批次3"]
end
subgraph "Stage 3: filter_items + chain"
H["符合条件的结果"]
end
A --> B & C & D
B --> E
C --> F
D --> G
E & F & G --> H
style A fill:#C7CEEA,stroke:#9FA8DA
style B fill:#FFDAB9,stroke:#FFAB76
style C fill:#FFDAB9,stroke:#FFAB76
style D fill:#FFDAB9,stroke:#FFAB76
style H fill:#B5EAD7,stroke:#80CBC4流式处理的优势:
- 内存高效:不需要同时加载所有数据
- 延迟低:第一批结果尽早可用
- 可中断:可以随时停止,无需等待全部完成
六、对比:迭代器 vs 列表
| 维度 | 迭代器 / 生成器 | 列表 |
|---|---|---|
| 内存占用 | O(1),常量内存 | O(n),随元素增加 |
| 惰性求值 | ✅ 按需产生 | ❌ 一次性产生 |
| 单向遍历 | ✅ 只能向前 | ✅ 任意位置 |
| 重用性 | ❌ 一次性,耗尽后需重建 | ✅ 可多次迭代 |
| 随机访问 | ❌ 不支持 | ✅ 支持索引访问 |
| 适用场景 | 大数据、流式处理、管道 | 小数据、需频繁索引 |
七、完整可运行代码
1 | import itertools |
核心要点:生成器和迭代器是 Python 异步和流式处理的基础。掌握它们,你才能真正理解为什么 LLM 输出是逐字出现的,以及如何在 AI 数据管道中写出既高效又内存友好的代码。
原创于 2026-04-25 | 所属系列:【Python AI教程】
📚 Python AI教程 系列导航
本文是《Python AI教程》系列第 3/14 篇。
| 方向 | 章节 |
|---|---|
| ◀ 上一篇 | (二)上下文管理器 |
| 下一篇 ▶ | (四)类型提示 |
📖 全部 14 篇目录(点击展开)
对比分析
本章核心是 Python 的迭代器协议与生成器(yield / yield from)。
维度一:生成器 vs 列表(流式 vs 全量)
| 方案 | 内存占用 | 延迟求值 | 适用场景 |
|---|---|---|---|
| 生成器 / 生成器表达式 | O(1)(仅保存当前状态) | ✅ 元素按需产出 | 流式 LLM 输出、大文件、无限序列 |
| 列表 / 列表推导 | O(N)(全部加载到内存) | ❌ 一次性构造 | 需多次遍历、需要 len()/索引 |
| map / filter 迭代器 | O(1) | ✅ | 简单数据变换 |
| itertools 链式迭代器 | O(1) | ✅ | 复杂流式 pipeline |
维度二:与其他语言的”惰性序列”
| 语言 | 惰性序列特性 | 与 Python 生成器对比 |
|---|---|---|
| Java | Stream API(Java 8+) | 语义最接近,支持 map/filter/reduce;缺点是只能消费一次、并行流行为有坑 |
| C++ | 范围 for + 自定义迭代器 | 灵活但样板代码多,泛型 lambda 时代稍好 |
| Go | channel + goroutine | 通过 CSP 模型实现流式;比生成器更”系统级” |
| Rust | Iterator Trait + iter()/into_iter() | 零成本抽象、组合子丰富(map/filter/fold);类型系统保证 |
| JavaScript | Generator Function(function*)+ yield | 语法与 Python 几乎一样;缺点是生态多以 Promise/async 为主 |
| Ruby | Enumerator / Fiber | 思路相同;Enumerator::Lazy 提供惰性链 |
| Haskell | 列表默认就是惰性的 | 语言层面原生支持,无须关键字 |
维度三:生成器 vs 协程
- 生成器(
yield):数据生产者,单向拉取 - async/await 协程:任务调度单位,可并发等待 I/O
- 生成器协程(
yield双向 send/throw):历史上 Python 2.2–3.3 的协程实现,已被async/await取代
优缺点小结
- Python 生成器:语法最轻、状态自动保存、
yield from委托优雅;缺点是单次消费、调试栈略长 - Java Stream:并行流开箱即用;缺点是不可复用、调试难
- Rust Iterator:零成本、组合子生态最强;缺点是生命周期标注对新手不友好
- JavaScript Generator:与 Python 高度相似;缺点是多数场景已被 async/await 替代
- Haskell 列表:语言级惰性、最纯粹;缺点是栈溢出风险
何时选
- 选 生成器:流式数据(LLM token 流、大文件、传感器数据)、无限序列
- 选 列表推导:需要多次索引/长度/切片
- 选 itertools.chain / islice / takewhile:组合多个数据源
- 选 async 生成器:异步流式(如
async for chunk in websocket) - 不推荐 手写迭代器类:除非自定义状态复杂,否则用
yield更短