Python 中 yield 的用法详解
——最简单、最清晰的解释(2026版,生成器 & 面试必看)
yield 是 Python 中最容易让人“一知半解”的关键字,但一旦真正理解,它会成为你写优雅、高性能代码的利器。
一句话记住 yield 的本质:
yield 让函数变成“可以暂停并保存现场的发电机”,而不是一次性把所有结果算完再返回。
下面用最直白的语言 + 对比 + 代码,把 yield 的各种用法讲透。
1. yield vs return —— 最核心区别
| 写法 | 执行到这里会发生什么 | 函数结束后状态 | 下次调用函数时从哪里继续 |
|---|---|---|---|
| return | 返回结果 + 立即结束函数 | 函数彻底死亡 | 从头开始 |
| yield | 返回结果 + 暂停函数,保存所有现场(变量、位置) | 函数进入“挂起”状态 | 从 yield 下一行继续执行 |
这就是 yield 能实现“惰性求值”“内存友好”“无限序列”的根本原因。
2. 最简单的例子 —— 从列表到生成器
普通函数(一次性返回全部)
def get_numbers():
return [1, 2, 3, 4, 5] # 一次性创建整个列表
nums = get_numbers()
for n in nums:
print(n)
改成生成器函数(用 yield)
def get_numbers():
for i in range(1, 6):
yield i # 每次只产生一个数
nums = get_numbers() # 此时 nums 是一个生成器对象
for n in nums:
print(n) # 1 2 3 4 5(按需产生,不占内存)
关键观察:
- 没有 yield → 返回 list(占用完整内存)
- 有 yield → 返回 generator(几乎不占内存,只在迭代时逐个产生)
3. yield 最经典的三大使用场景
场景1:内存友好的大数据/文件逐行读取
def read_large_file(filename):
with open(filename, 'r', encoding='utf-8') as f:
for line in f: # 不会一次性把文件全部读进内存
yield line.strip()
for line in read_large_file("very_big.log"):
process(line) # 每次只在内存中处理一行
场景2:无限序列 / 惰性序列
def fibonacci():
a, b = 0, 1
while True:
yield a
a, b = b, a + b
fib = fibonacci()
for _ in range(10):
print(next(fib)) # 0 1 1 2 3 5 8 13 21 34
想取第 10000 个斐波那契数?
普通列表方式会先把前面 9999 个都算出来并存内存,而 yield 方式只算到第 10000 个为止。
场景3:协程 / 生成器 + send()(高级用法,面试常考)
def talker():
while True:
name = yield # 暂停在这里,等外部 send 值
print(f"你好,{name}!")
t = talker()
next(t) # 必须先启动生成器(到第一个 yield)
t.send("Alice") # → 你好,Alice!
t.send("Bob") # → 你好,Bob!
这就是 Python 协程最原始的实现方式(async/await 底层就是基于这种机制)。
4. yield from(Python 3.3+)—— 委托生成器
最优雅的写法,避免手动 for 循环 yield。
def chain(*iterables):
for it in iterables:
yield from it # 相当于 for x in it: yield x
list(chain("ABC", [1,2,3])) # ['A','B','C',1,2,3]
5. 面试必考的 8 个核心知识点(背下来基本稳)
- yield 出现后函数变成生成器函数,调用它返回的是生成器对象(generator),不是执行结果。
- 生成器只能迭代一次,迭代完后就耗尽(exhausted),再次迭代为空。
- next() 或 for 循环会让生成器从暂停处继续执行,直到下一个 yield 或函数结束。
- StopIteration 异常:生成器跑完所有 yield 后抛出,for 循环会自动捕获。
- yield 表达式可以接收值(通过 send()),这是协程的基础。
- yield from 可以委托给另一个可迭代对象或生成器。
- 生成器表达式 vs 生成器函数:
(x**2 for x in range(10))→ 生成器表达式(小括号)def gen(): yield ...→ 生成器函数(更灵活)
- 内存优势:生成器适合处理无法一次性装进内存的数据流(大文件、实时数据、爬虫响应等)
6. 2026 年写生成器的现代最佳实践
# 推荐写法(类型提示 + 文档)
from collections.abc import Generator
from typing import Any
def countdown(n: int) -> Generator[int, None, None]:
"""从 n 倒计时到 0(包含 0)"""
while n >= 0:
yield n
n -= 1
# 使用
for num in countdown(5):
print(num) # 5 4 3 2 1 0
7. 一句话记住 yield
yield = “我先把这个值给你,你用完再来找我,我接着从刚才停的地方继续干活”
一旦你能用这个心态去理解 yield,你就真正掌握它了。
想继续看 yield 的进阶用法吗?
比如:
- yield 在 asyncio 协程中的底层原理
- 生成器 + contextmanager 实现资源管理
- send() + throw() + close() 完整用法
- yield from vs async for 的关系
或者你有具体代码想用 yield 优化,直接贴出来,我帮你改成生成器版本。