yield通过每次只生成一行或一块数据,使函数变为生成器,实现流式读取大文件避免内存溢出;关键在于控制每次产出的数据量,而非yield本身自动省内存。

yield 怎么让大文件不爆内存
直接用 open() 读大文件再 readlines(),Python 会把整份内容塞进内存,动辄几个 GB 就触发 MemoryError。用 yield 的核心是:每次只生成一行(或一块)数据,函数变成生成器,调用时不执行,只返回一个迭代器。
关键不是“用了 yield 就自动省内存”,而是你得控制每次产出的数据量。比如逐行 yield 是最常见也最安全的起点:
def read_large_file(filepath):
with open(filepath, 'r', encoding='utf-8') as f:
for line in f: # 文件对象本身是迭代器,不加载全文
yield line.strip()
注意这里没用 f.readlines(),否则又全读进来了;for line in f 是底层按缓冲区逐块读取、按行切分,配合 yield 实现真正的流式。
yield 和 return 混用会出什么错
生成器函数里一旦出现 return(哪怕不带值),函数就会立即终止并抛出 StopIteration——这不是错误,但容易误判为“提前结束”。更隐蔽的问题是:如果在 yield 后面写 return value,这个 value 不会作为返回值被拿到,而是被忽略(Python 3.3+ 才支持 return expr 传给 StopIteration.value,但绝大多数场景不需要)。
常见踩坑点:
- 误以为
return "done"能被调用方接收到,实际只能靠捕获StopIteration异常去取,非常不直观 - 在循环中混用
yield和return,导致部分数据没 yield 就退出 - 想“先 yield 再清理”,结果把清理逻辑写在 yield 后面又没加异常处理,资源可能泄漏
建议:生成器里只用 yield,收尾逻辑(如 close、flush)放在 with 语句里,靠上下文管理器保证执行。
怎么按块(chunk)而不是按行 yield
有些文件没有明确换行(比如二进制日志、JSON Lines 不规范、超长单行文本),按行 yield 失效,就得手动分块读取。这时候 yield 的单位不再是“行”,而是“字节块”或“记录块”。
示例:每次读 8192 字节,yield 解码后的字符串片段(注意边界截断问题):
def read_in_chunks(filepath, chunk_size=8192):
with open(filepath, 'rb') as f: # 用 binary mode 避免解码干扰
while True:
data = f.read(chunk_size)
if not data:
break
yield data.decode('utf-8', errors='ignore')
要点:
- 用
rb模式读,避免编码错误打断流程;errors='ignore'或'replace'防止个别坏字节 crash - chunk_size 不是越大越好:太大仍可能占内存;太小则系统调用频繁,IO 效率低。8KB–64KB 是较稳妥范围
- 如果业务需要完整记录(如 JSON 对象),不能简单按字节切,得配合解析器边读边判断边界,这时 yield 的就不是 raw bytes,而是解析后的 dict/list
生成器嵌套时 yield from 别漏掉
当你要组合多个文件、多个数据源,或者把预处理逻辑拆成子生成器时,直接 yield func() 会 yield 出一个生成器对象,而不是它的内容——结果是外层拿到的是 generator object,不是数据。
必须用 yield from 委托子生成器:
def process_files(filepaths):
for path in filepaths:
yield from read_large_file(path) # ✅ 正确:展开子生成器
<p>def bad_example(filepaths):
for path in filepaths:
yield read_large_file(path) # ❌ 错误:yield 出 generator 对象</p>
另外注意:yield from 在 Python 3.3+ 才支持;如果要兼容旧版本,得手动循环 + yield:
for item in read_large_file(path):
yield item
嵌套层级深了之后,调试时容易搞不清哪一层 yield 了什么,建议每个生成器函数名体现其粒度,比如 yield_lines()、yield_records()、yield_parsed_dicts()。
真正难的不是写 yield,而是设计好每层 yield 的数据契约:上游拿什么,下游期望什么,中间要不要做 decode / split / validate —— 这些不厘清,流式结构反而会让 bug 更难定位。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











