1. 生成器是什么? #
生成器(generator) 是一种特殊的迭代器 ,通过 yield 的函数或 生成器表达式 (x for x in ...) 创建。
与普通函数一次 return 全部结果不同,生成器按需产出 下一个值(惰性求值),算完一个再算下一个。
不把整份数据常驻内存 ,适合大文件逐行读、百万级记录分批写库、多步清洗管道等场景。
与迭代器章节衔接:所有生成器都是迭代器,只能完整消费一次 。
业务里多数时候用生成器表达式或简单 yield 分批即可,不必深究协议细节。
两种创建方式对比如下。
方式
写法
典型场景
生成器函数
def f(): yield x
自定义分批、过滤、读文件
生成器表达式
(x for x in items)
简单映射/过滤
2. 生成器函数:yield #
在函数体内写 yield 值 ,该函数就变成生成器函数 。
调用时不立即跑完函数体 ,而是返回一个生成器对象 。
由 for 或 next() 驱动:执行到 yield 时暂停 并把值交给调用方,下次从暂停处继续 执行,直到函数结束。
适合把「读文件、分批、过滤」写成可复用的惰性序列。
下面示例:逐行读日志;以及按固定大小 yield batch 分批处理用户 ID(写库、调接口时常用)。
def read_lines (path ):
with open (path, "r" , encoding="utf-8" ) as f:
for line in f:
yield line.strip()
for line in read_lines("access.log" ):
print (line)
def batch_items (items, size=100 ):
"""把可迭代对象按批产出,便于分批写库、调接口"""
batch = []
for item in items:
batch.append(item)
if len (batch) >= size:
yield batch
batch = []
if batch:
yield batch
def bulk_update (chunk ):
"""批量更新用户"""
print (f"Bulk updating {len (chunk)} users: {chunk} " )
user_ids = list (range (1 , 2100 ))
for chunk in batch_items(user_ids, size=500 ):
bulk_update(chunk)
普通函数 return
生成器 yield
结果
一次返回全部
多次逐个返回
内存
常需先构造完整列表
惰性,省内存
复用
可多次调用
生成器对象只能遍历一次
3. 生成器表达式 #
语法与列表推导式相同,但用圆括号 :(expr for x in iterable if cond) 。
不立刻生成整张列表,而是返回生成器。
常与 sum() 、any() 、max() 等直接配合,例如 `sum(x 2 for x in range(n))`**。
数据量大且只遍历一次 时用生成器表达式。
需要多次访问、按下标取值或 len() 时改用列表推导式 。
squares_list = [x ** 2 for x in range (1000000 )]
squares_gen = (x ** 2 for x in range (1000000 ))
total = sum (x ** 2 for x in range (1000000 ))4. yield from #
yield from 可迭代对象 把产出元素的工作委托 给子生成器或其它可迭代对象。
等价于内层写 for item in sub: yield item,代码更短。
适合串联多个数据源 、递归遍历树形结构。
日常 CRUD 里不如直接 for 常见,读框架或工具库源码时会遇到。
def chain_sources (*sources ):
"""
接收多个可迭代对象,依次遍历所有元素。
"""
for src in sources:
yield from src
for item in chain_sources([1 , 2 , 3 ], [4 , 5 , 6 ]):
print (item)5. 项目中的典型场景 #
生成器在业务里多用于控制内存 和分步管道 。
大文件不 readlines()、批量 ID 不一次 list(queryset)、解析 JSONL 后逐条过滤再写入。
大文件 / 日志 :yield 逐行或分块读取(也可直接 for line in f,复杂过滤可包一层生成器)。
分批处理 :batch_items 模式,消息队列消费。
数据管道 :读取 → 清洗 → 写入,每步用生成器串联,避免中间大列表。
与内置函数配合 :sum(...)、any(...)、max(...) 内写 (x for x in data)。
下面给出「解析 → 过滤 → 消费」的管道示例。
import json
def parse_rows (lines ):
for line in lines:
if not line:
continue
yield json.loads(line)
def valid_users (rows ):
for row in rows:
if row.get("active" ):
yield row
for user in valid_users(parse_rows(["{\"name\": \"Alice\", \"active\": true}" , "{\"name\": \"Bob\", \"active\": false}" ])):
print (user)6. 项目开发要点 #
生成器是进阶但实用的工具:用对了省内存,用错了会踩「只能遍历一次」「没有 len」等坑。
大数据用生成器或生成器表达式 ,避免 list(百万条查询)。
yield 写可读的分步逻辑 ,比嵌套推导式清晰时分批、过滤。
生成器只能消费一次 ;要再遍历就重新调用函数或 list()(确认内存可接受)。
不要误用 :生成器没有 len(),也不能按下标访问;需要长度或复用时再 list(gen)(确认内存可接受)。
异常与资源 :with open 放在生成器内,或调用方保证关闭;yield 中途异常要注意文件是否已关。
7. 总结 #
本章核心:yield 与 (x for …) 实现惰性序列 ,省内存、适合分批与管道。
生成器即迭代器,消费一次 。
下表可作速查。
概念
要点
yield
暂停并返回值,函数变生成器
惰性
省内存,适合大文件、大批量
表达式 (… for …)
简单转换
yield from
委托子迭代器
分批 yield batch
写库、调 API 常用模式