Python中 yield 的用法详解 —— 2026年最简单、最清晰的版本
yield 是 Python 中最神奇、最有价值的关键词之一。
它让一个普通函数秒变生成器(generator),带来内存节省 + 惰性计算 + 状态保持三大超级能力。
下面用最直白的语言 + 递进式例子帮你彻底搞懂(面试/日常开发都够用)。
1. 最核心的一句话理解 yield
把
return改成yield,函数就从“一次性把所有结果算完再给你”
变成了“算一个给你一个,暂停在那等你下次再来要”
# 普通函数(一次性返回所有)
def odds(n):
result = []
for i in range(n):
if i % 2 == 1:
result.append(i)
return result
# 用 yield 改写 → 生成器
def odds_gen(n):
for i in range(n):
if i % 2 == 1:
yield i # 每次只产生一个,暂停
使用对比:
print(odds(10)) # [1,3,5,7,9] — 一次性全在内存
g = odds_gen(10)
print(list(g)) # [1,3,5,7,9] — 但中间只在内存中存在一个数
内存杀手级差异:当 n=1亿 时,列表版会吃爆内存,生成器版几乎不占额外内存。
2. yield 到底做了什么?(执行流程图解)
def simple_gen():
print("开始")
yield 100
print("又回来了")
yield 200
print("结束了")
# 没有更多 yield 了,自动 raise StopIteration
g = simple_gen() # 调用函数 → 并不执行函数体,只得到一个生成器对象
print(next(g)) # → "开始" 被打印 → 输出 100 → 暂停在 yield 100 后面
print(next(g)) # → "又回来了" 被打印 → 输出 200 → 暂停
# print(next(g)) # → "结束了" 被打印 → 抛出 StopIteration
一句话总结执行顺序:
- 调用函数 → 返回生成器对象(不执行函数体)
- 第一次
next()或for→ 从头执行到第一个yield→ 返回 yield 的值 → 暂停 - 第二次
next()→ 从上次暂停处继续 → 到下一个yield→ 返回值 → 暂停 - 没有更多 yield → 抛
StopIteration(for 循环会自动处理)
3. 最常用的三种写法(记住这三种就够 90% 场景)
写法1:最基础版(产生序列)
def fib(n):
a, b = 0, 1
while a < n:
yield a
a, b = b, a + b
for x in fib(100):
print(x, end=' ') # 0 1 1 2 3 5 8 13 ...
写法2:yield + 列表推导式(最优雅)
# 普通列表推导式(占内存)
squares = [x*x for x in range(10000000)]
# 生成器表达式(几乎不占内存)
squares_gen = (x*x for x in range(10000000))
print(next(squares_gen)) # 0
print(next(squares_gen)) # 1
写法3:yield from(委托给另一个可迭代对象) —— Python 3.3+ 引入,超级实用
def chain():
yield from [1, 2, 3]
yield from range(5, 8)
yield from "abc"
print(list(chain()))
# [1,2,3,5,6,7,'a','b','c']
等价于(但更清晰):
def chain_old():
for x in [1,2,3]: yield x
for x in range(5,8): yield x
for x in "abc": yield x
4. 进阶但很常用的:生成器可以接收值(.send())
def talker():
while True:
cmd = yield # yield 出现在 = 右边 → 可以接收值
print(f"收到指令:{cmd}")
g = talker()
next(g) # 必须先启动到第一个 yield(重要!)
g.send("开灯") # 收到指令:开灯
g.send("关门") # 收到指令:关门
经典协程风格写法(早期协程就是这么玩的):
def accumulator():
total = 0
while True:
value = yield total # 接收值 → 赋值给 value
total += value or 0
acc = accumulator()
next(acc) # 启动 → 得到 0
print(acc.send(10)) # 10
print(acc.send(30)) # 40
print(acc.send(5)) # 45
5. 面试/工作中最常被问的 8 个点(2026版)
| 排名 | 问题 | 核心回答一句话 |
|---|---|---|
| 1 | yield 和 return 区别? | return 结束函数并返回值;yield 返回值+暂停函数,下次可继续执行 |
| 2 | 生成器和迭代器区别? | 生成器一定是迭代器,但迭代器不一定是生成器(生成器是用 yield 函数实现的迭代器) |
| 3 | 第一次调用生成器函数发生了什么? | 什么都没执行,只返回了一个生成器对象 |
| 4 | 怎么启动生成器? | next(g) 或 for 循环(for 会自动处理 StopIteration) |
| 5 | yield from 有什么用? | 委托给另一个可迭代对象,简化多层 yield,自动处理子生成器的返回值 |
| 6 | 生成器可以用多次吗? | 不可以,耗尽后就废了(除非重新生成一个) |
| 7 | 生成器表达式和列表推导式区别? | () 是生成器,[] 是列表;前者惰性,后者立即计算 |
| 8 | 什么时候用 yield 而不是 return? | 需要惰性计算、节省内存、处理大数据流、实现协程/管道、需要中途暂停恢复时 |
6. 2026年一句话总结(送给未来的你)
yield 是 Python 送给程序员的“时间暂停器 + 内存魔法棒”
用它,你可以写出“看起来像列表,用起来像流”的优雅代码。
记住这个最简心法:
- 想一次性全算完 → 用 return + 列表
- 想边算边用、内存友好 → 用 yield + 生成器
- 想委托给别人 → 用 yield from
需要我再给你出 5 道 yield 面试真题带答案吗?或者想看某个具体场景的完整代码?直接说~