03-生成器基础
Python 版本要求:Python 3.11+
贯穿项目:日志分析系统
本节目标:用 yield 实现日志解析生成器,内存友好
概念铺垫
为什么需要生成器?
问题场景:处理千万级数据
python
import sys
from typing import Iterator
# ❌ 列表方式:内存爆炸
def squares_list(n: int) -> list[int]:
return [x ** 2 for x in range(n)]
result = squares_list(10_000_000)
print(f"列表内存:{sys.getsizeof(result) / 1024 / 1024:.2f} MB") # 约 80 MB!
# ✅ 生成器方式:内存友好
def squares_generator(n: int) -> Iterator[int]:
for x in range(n):
yield x ** 2
gen = squares_generator(10_000_000)
print(f"生成器内存:{sys.getsizeof(gen)} 字节") # 约 200 字节!生成器的优势:
- 惰性求值:只在需要时才计算
- 内存友好:不一次性生成所有数据
- 代码简洁:用
yield代替复杂的迭代器类
yield 概念:暂停点打卡
生活类比
┌─────────────────────────────────────────┐
│ yield vs return │
├─────────────────────────────────────────┤
│ │
│ return = 下班打卡 │
│ • 做完就走 │
│ • 不再回来 │
│ • 只返回一次 │
│ │
│ yield = 暂停点打卡 │
│ • 做一步,歇一步 │
│ • 下次从这里继续 │
│ • 可以返回多次 │
│ │
│ 示例: │
│ def work(): │
│ yield "做完第1步" # 打卡,暂停 │
│ yield "做完第2步" # 继续,再打卡 │
│ yield "做完第3步" # 继续,再打卡 │
│ │
└─────────────────────────────────────────┘一句话:
yield= 暂停并返回值,下次从这里继续执行
yield vs return
python
from typing import Iterator
def with_return(n: int) -> int:
"""return:立即结束"""
for i in range(n):
return i # 只返回第一个值
def with_yield(n: int) -> Iterator[int]:
"""yield:暂停返回"""
for i in range(n):
yield i # 返回值,暂停等待下次
print(with_return(5)) # 0(只返回一次)
print(list(with_yield(5))) # [0, 1, 2, 3, 4](返回多次)L1 理解层:会用
yield 执行流程时间线
┌─────────────────────────────────────────────────────────────┐
│ yield 执行时间线 │
│ │
│ 代码: │
│ yield 1 │
│ yield 2 │
│ yield 3 │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ gen = simple_gen() ─→ 创建生成器 │ │
│ │ ⚠️ 函数不执行!只是创建对象 │ │
│ │ │ │ │
│ │ ↓ │ │
│ │ next(gen) ────────→ 执行到 yield 1 │ │
│ │ │ 返回 1,暂停 │ │
│ │ │ 状态:GEN_SUSPENDED │ │
│ │ ↓ │ │
│ │ next(gen) ────────→ 从暂停处继续 │ │
│ │ │ 执行到 yield 2 │ │
│ │ │ 返回 2,暂停 │ │
│ │ ↓ │ │
│ │ next(gen) ────────→ 从暂停处继续 │ │
│ │ │ 执行到 yield 3 │ │
│ │ │ 返回 3,暂停 │ │
│ │ ↓ │ │
│ │ next(gen) ────────→ 从暂停处继续 │ │
│ │ │ 函数结束 │ │
│ │ │ 抛出 StopIteration │ │
│ │ │ 状态:GEN_CLOSED │ │
│ │ │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ ⚠️ 关键:创建生成器时函数不执行 │
│ ⚠️ 关键:首次 next() 才开始执行 │
│ │
└─────────────────────────────────────────────────────────────┘最简示例
python
from typing import Iterator
def simple_generator() -> Iterator[int]:
yield 1
yield 2
yield 3
gen = simple_generator() # 创建生成器(函数不执行!)
print(next(gen)) # 1
print(next(gen)) # 2
print(next(gen)) # 3
print(next(gen, "耗尽")) # "耗尽"带日志的执行流程
python
from typing import Iterator
def logged_generator() -> Iterator[int]:
print(">>> 开始执行")
yield 1
print(">>> 继续执行")
yield 2
print(">>> 再次执行")
yield 3
print(">>> 结束执行")
gen = logged_generator()
print("生成器已创建") # 函数不执行!
print(f"返回: {next(gen)}") # >>> 开始执行,返回: 1
print(f"返回: {next(gen)}") # >>> 继续执行,返回: 2
print(f"返回: {next(gen)}") # >>> 再次执行,返回: 3
print(f"返回: {next(gen, '耗尽')}") # >>> 结束执行,返回: 耗尽贯穿实战:日志解析生成器
python
from typing import Iterator
from datetime import datetime
def parse_log_entries(filename: str) -> Iterator[dict]:
"""解析日志文件的生成器
贯穿项目:日志分析系统第3步
功能:逐行解析日志,产出结构化数据
内存:无论文件多大,只占用当前行的内存
日志格式示例:
[2024-01-19 10:00:00] ERROR user_login - 用户登录失败
"""
with open(filename, "r", encoding="utf-8") as f:
for line in f:
if not line.strip():
continue
if line.startswith("["):
timestamp_str = line[1:20]
try:
timestamp = datetime.strptime(
timestamp_str, "%Y-%m-%d %H:%M:%S"
)
except ValueError:
timestamp = None
parts = line[22:].split(" - ", 1)
level = parts[0].strip() if parts else "UNKNOWN"
message = parts[1] if len(parts) > 1 else ""
yield {
"timestamp": timestamp,
"level": level,
"message": message.strip(),
"raw": line.strip()
}
# 使用:处理大日志文件
for entry in parse_log_entries("app.log"):
if entry["level"] == "ERROR":
print(f"{entry['timestamp']}: {entry['message'][:50]}")关键代码说明:
| 代码 | 含义 |
|---|---|
for line in f | 文件对象是迭代器,逐行读取 |
yield {...} | 每解析一行就产出,不等待全部解析完 |
try/except ValueError | 跳过格式错误的时间戳,继续处理 |
L1 理解层:生成器基础
生成器函数示例
倒数生成器
python
from typing import Iterator
def countdown(n: int) -> Iterator[int]:
"""倒数生成器"""
while n > 0:
yield n
n -= 1
for num in countdown(5):
print(num, end=" ")
# 5 4 3 2 1斐波那契生成器
python
from typing import Iterator
def fibonacci(limit: int | None = None) -> Iterator[int]:
"""斐波那契数列生成器"""
a, b = 0, 1
count = 0
while limit is None or count < limit:
yield a
a, b = b, a + b
count += 1
for num in fibonacci(10):
print(num, end=" ")
# 0 1 1 2 3 5 8 13 21 34无限素数生成器
python
from typing import Iterator
import math
def primes() -> Iterator[int]:
"""无限素数生成器"""
n = 2
while True:
if all(n % i != 0 for i in range(2, int(math.sqrt(n)) + 1)):
yield n
n += 1
for i, prime in enumerate(primes()):
if i >= 10:
break
print(prime, end=" ")
# 2 3 5 7 11 13 17 19 23 29生成器表达式
python
from typing import Iterator
# 列表推导式:立即生成(方括号)
squares_list = [x ** 2 for x in range(5)]
print(squares_list) # [0, 1, 4, 9, 16]
# 生成器表达式:惰性生成(圆括号)
squares_gen = (x ** 2 for x in range(5))
print(list(squares_gen)) # [0, 1, 4, 9, 16]内存对比:
python
import sys
list_data = [x for x in range(1_000_000)]
gen_data = (x for x in range(1_000_000))
print(f"列表:{sys.getsizeof(list_data) / 1024:.2f} KB") # 约 8 MB
print(f"生成器:{sys.getsizeof(gen_data)} 字节") # 约 200 字节L2 实践层:用好
推荐做法
| 做法 | 原因 | 示例 |
|---|---|---|
| 生成器函数代替迭代器类 | 代码简洁,Python 自动处理协议 | def gen(): yield x |
| 生成器表达式用于惰性计算 | 内存友好 | (x ** 2 for x in range(n)) |
| 列表推导式用于多次访问 | 列表可重复遍历 | [x ** 2 for x in range(n)] |
| 用 yield from 委托子生成器 | 简化嵌套迭代 | yield from sub_gen() |
| 用 finally 清理资源 | 生成器关闭时执行 | try: yield x; finally: cleanup() |
反模式:不要这样做
python
# ❌ 生成器表达式只为了遍历(不创建有意义结果)
result = (print(x) for x in range(10))
for _ in result:
pass
# ✅ 正确:直接用 for 循环
for x in range(10):
print(x)python
# ❌ 生成器耗尽后继续使用
gen = (x for x in range(3))
print(list(gen)) # [0, 1, 2]
print(list(gen)) # [] ← 已耗尽!
# ✅ 正确:需要多次使用时用列表
data = [x for x in range(3)]
print(data) # [0, 1, 2]
print(data) # [0, 1, 2]python
# ❌ 无限生成器没有终止条件
def infinite():
while True:
yield 1
for x in infinite(): # 无限循环!
print(x)
# ✅ 正确:添加终止条件
for i, x in enumerate(infinite()):
if i >= 100:
break
print(x)列表推导式 vs 生成器表达式
┌─────────────────────────────────────────────────────────────┐
│ 列表推导式 vs 生成器表达式选择 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 列表推导式 [x for x in iterable] │
│ ───────────────────────────────────── │
│ • 立即生成所有元素 │
│ • 可以多次遍历 │
│ • 支持索引访问、len() │
│ • 适用:小数据、需要多次访问、需要索引 │
│ │
│ 生成器表达式 (x for x in iterable) │
│ ───────────────────────────────────── │
│ • 惰性生成,按需产出 │
│ • 只能遍历一次 │
│ • 不支持索引、len() │
│ • 适用:大数据、流式处理、一次性使用 │
│ │
│ 选择决策: │
│ 数据量小 + 多次访问 → 列表推导式 │
│ 数据量大 + 一次性处理 → 生成器表达式 │
│ │
└─────────────────────────────────────────────────────────────┘适用场景
| 场景 | 推荐方式 | 原因 |
|---|---|---|
| 大数据集处理 | 生成器表达式 | 内存友好 |
| 文件逐行读取 | 生成器函数 | 流式处理 |
| 数据管道 | 生成器函数 | 惰性传递 |
| 无限序列 | 生成器函数 | 按需生成 |
| 小数据多次访问 | 列表推导式 | 支持重复遍历 |
| 需要索引访问 | 列表推导式 | 列表支持索引 |
L3 专家层:深入
生成器内部结构
python
import inspect
def simple_gen():
x = 1
yield x
y = 2
yield y
return "done"
gen = simple_gen()
# 查看生成器属性
print(f"代码对象:{gen.gi_code}")
print(f"帧对象:{gen.gi_frame}")
print(f"运行状态:{gen.gi_running}")
# 帧对象保存局部变量
print(next(gen))
print(f"局部变量:{gen.gi_frame.f_locals}") # {'x': 1}
print(next(gen))
print(f"局部变量:{gen.gi_frame.f_locals}") # {'x': 1, 'y': 2}┌─────────────────────────────────────────────────────────────┐
│ 生成器内部结构 │
├─────────────────────────────────────────────────────────────┤
│ │
│ generator 对象: │
│ ├── gi_code → 代码对象(编译后的字节码) │
│ ├── gi_frame → 帧对象(保存局部变量、执行位置) │
│ ├── gi_running → 运行状态(是否正在执行) │
│ └── gi_yieldfrom → 委托的子生成器 │
│ │
│ 帧对象 (Frame): │
│ ├── f_locals → 局部变量字典 │
│ ├── f_lineno → 当前执行行号 │
│ ├── f_lasti → 最后执行的指令索引 │
│ │
│ 生成器状态: │
│ GEN_CREATED → 刚创建,未启动 │
│ GEN_RUNNING → 正在执行 │
│ GEN_SUSPENDED → 暂停在 yield │
│ GEN_CLOSED → 已关闭 │
│ │
└─────────────────────────────────────────────────────────────┘生成器状态变化
python
from inspect import getgeneratorstate
def state_demo():
yield 1
yield 2
gen = state_demo()
print(f"创建后:{getgeneratorstate(gen)}") # GEN_CREATED
next(gen)
print(f"暂停后:{getgeneratorstate(gen)}") # GEN_SUSPENDED
next(gen)
print(f"再暂停:{getgeneratorstate(gen)}") # GEN_SUSPENDED
try:
next(gen)
except StopIteration:
pass
print(f"耗尽后:{getgeneratorstate(gen)}") # GEN_CLOSEDyield 字节码
python
import dis
def yield_demo():
x = 1
yield x
x = x + 1
yield x
dis.dis(yield_demo)
# 输出包含 YIELD_VALUE 指令┌─────────────────────────────────────────────────────────────┐
│ yield 相关字节码 │
├─────────────────────────────────────────────────────────────┤
│ │
│ YIELD_VALUE → 暂停并返回值 │
│ YIELD_FROM → 委托给子生成器 │
│ GET_YIELD_FROM_ITER → 获取 yield from 目标 │
│ │
│ yield x 执行过程: │
│ 1. LOAD_FAST x → 加载变量 x │
│ 2. YIELD_VALUE → 暂停,返回 x │
│ 3. (下次恢复) → 继续执行下一条指令 │
│ │
└─────────────────────────────────────────────────────────────┘性能考量
| 操作 | 时间复杂度 | 空间复杂度 | 说明 |
|---|---|---|---|
| 创建生成器 | O(1) | O(200字节) | 只分配对象和帧 |
| yield 暂停/恢复 | O(1) | O(1) | 保存/恢复帧状态 |
| yield from 委托 | O(1)传递 | O(1) | 值直接传递 |
python
import timeit
list_time = timeit.timeit(lambda: sum([x for x in range(1_000_000)], number=10)
gen_time = timeit.timeit(lambda: sum(x for x in range(1_000_000)], number=10)
print(f"列表:{list_time:.3f}s")
print(f"生成器:{gen_time:.3f}s")为什么 Python 选择 yield?
┌─────────────────────────────────────────────────────────────┐
│ yield 设计动机 │
├─────────────────────────────────────────────────────────────┤
│ │
│ ① 简化迭代器实现 │
│ 迭代器类需要 __iter__、__next__、状态管理 │
│ 生成器函数只需 yield,Python 自动处理协议 │
│ │
│ ② 惰性求值需求 │
│ 大数据处理场景普遍 │
│ 无限序列(斐波那契、素数)需要 │
│ 文件流、网络流处理 │
│ │
│ ③ 协程基础 │
│ yield 是协程的早期形式 │
│ async/await 基于类似机制 │
│ send() 实现双向通信 │
│ │
│ ④ 代码可读性 │
│ yield 位置清晰标识暂停点 │
│ 无需复杂的索引和状态变量 │
│ │
└─────────────────────────────────────────────────────────────┘自检清单
回答以下问题,检查你是否掌握了核心概念:
gen = func()会执行函数吗?yield和return有什么区别?- 生成器表达式和列表推导式有什么区别?
- 为什么生成器只能遍历一次?
答案:
- 不会,只创建生成器对象
- yield 暂停并返回多次,return 结束并返回一次
- 生成器表达式用圆括号、惰性;列表推导式用方括号、立即
- 生成器不存储数据,只维护执行状态,用完即弃
本章能力清单
学完本章,你能够:
- [x] 用 yield 创建生成器函数
- [x] 理解 yield 的暂停/恢复机制
- [x] 区分列表推导式和生成器表达式
- [x] 用生成器处理大文件和数据流
下一步学习:
- yield from 委托 → 第4节
- itertools 高效处理 → 第5节
本章小结
┌─────────────────────────────────────────────────────────────┐
│ 生成器基础 知识要点 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 核心概念: │
│ ✓ yield:暂停点打卡(返回值,暂停等待下次) │
│ ✓ 生成器:惰性求值(内存友好) │
│ │
│ 执行流程: │
│ ✓ 创建时不执行 │
│ ✓ next() 开始执行 │
│ ✓ yield 暂停返回 │
│ ✓ 函数结束 StopIteration │
│ │
│ 关键区别: │
│ ✓ 列表推导式 [x for x] - 立即生成 │
│ ✓ 生成器表达式 (x for x) - 惰性生成 │
│ │
│ 应用场景: │
│ ✓ 大数据处理 │
│ ✓ 文件逐行读取 │
│ ✓ 无限序列 │
│ ✓ 数据管道 │
│ │
└─────────────────────────────────────────────────────────────┘