Yield 函数
以下是一份 最清晰、最实用、最容易理解的 yield 讲解,带超多示例,一看就会。
yield 是 Python 用来创建 生成器(generator)函数 的关键字。
带有 yield 的函数不会一次性运行完毕,而是一次返回一个值,每次暂停在 yield 这里,下次继续运行。
是 “可以暂停的 return”。
yield = return 一个值 + 暂停函数执行,下次继续从暂停处运行。
def gen():
yield 1
yield 2
yield 3
g = gen()
print(next(g)) # 1
print(next(g)) # 2
print(next(g)) # 3
解释:
- gen() 不会立即执行
- 第一次 next(g) -> 执行到 yield 1 -> 返回 1 -> 暂停
- 第二次 next(g) -> 从暂停处继续执行 -> 返回 2 -> 再暂停
- 第三次 next(g) -> 返回 3
- 再 next -> StopIteration
普通函数:
- return 直接结束整个函数
- 想返回多个值只能用列表、元组等一次性全部返回
生成器:
- 可以逐个生成
- 节省内存
- 可处理无限大、流式数据
def read_lines(filename):
with open(filename) as f:
for line in f:
yield line.rstrip()
for line in read_lines("huge.log"):
print(line)
不用一次性加载整个文件,非常节省内存。
def count():
n = 1
while True:
yield n
n += 1
c = count()
print(next(c)) # 1
print(next(c)) # 2
print(next(c)) # 3
可生成无限序列!
让生成器“嵌套”更优雅:
def gen1():
yield from [1, 2, 3]
yield from [4, 5]
print(list(gen1()))
输出:
[1, 2, 3, 4, 5]
生成器不仅能返回值,还能接收值:
def echo():
while True:
x = yield
print("Got:", x)
g = echo()
next(g) # 启动生成器
g.send("hello") # Got: hello
g.send("world") # Got: world
| 场景 | 为什么用 yield |
|---|---|
| 读取大文件/流式数据 | 不占太多内存 |
| 无限序列 | 不需要一次性返回 |
| 管道式数据处理(类似 Linux pipe) | 优雅、可组合 |
| 生成器表达式 | 更节省内存 |
| 异步编程(历史上 asyncio) | yield from/await 的前身 |
yield 会让函数变成一个 状态机:
- 执行到 yield 时 -> 返回值给调用者 -> 暂停函数
- 下次调用 next() -> 恢复执行 -> 从 yield 后继续运行
你可以理解成 “返回值 + 暂停”。
因为生成器实现了迭代协议:
__iter____next__
for 自动调用 next()。
def generate_items():
for i in range(10):
yield i
def even_numbers(nums):
for n in nums:
if n % 2 == 0:
yield n
def combined():
yield from gen_a()
yield from gen_b()
- yield 让函数变成生成器
- 生成器“按需生成”值
- 执行到 yield 暂停,下次继续
- 节省内存、适合大数据流
- 可无限序列
- 适合管道式编程
遍历生成数据
def generate_items():
for i in range(10):
yield i
生成的数据在哪里?如何获取?
yield 生成的数据 不会自动存起来,它是惰性生成的,你必须主动“取”出来。
你可以把它理解为:
👉 生成器不会一次性返回所有数据,而是每次取一个。
def generate_items():
for i in range(10):
yield i
这是一个生成器函数,调用它后不会执行函数,而是返回一个 生成器对象。
for x in generate_items():
print(x)
输出:
0
1
2
...
9
items = list(generate_items())
print(items)
输出:
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
gen = generate_items()
print(next(gen)) # 0
print(next(gen)) # 1
print(next(gen)) # 2
直到耗尽会抛 StopIteration。
a, b, c, *rest = generate_items()
print(a, b, c, rest)
输出:
0 1 2 [3,4,5,6,7,8,9]
调用 generate_items() 不会执行函数,而是生成一个对象:
gen = generate_items()
print(gen)
输出:
<generator object generate_items at 0x...>
你必须用:
- for
- next()
- list()
- 解包
来“消费”它。
gen = generate_items()
list(gen)
list(gen) # 第二次是空的
因为第一次已经全部取完。
如果你只是要遍历:
for item in generate_items():
print(item)
如果你要收集成列表:
items = list(generate_items())
代码
def lazy_numbers(start, end):
while start < end:
yield start
start += 1
把 start += 1 放在 yield start 之后,是为了保证生成的序列顺序正确、值不会跳过。
下面给你详细解释:
yield start 会 先返回当前值,然后 start += 1 再让下次迭代返回下一个值。
如果你把 start += 1 放前面,会导致少一个值 / 第一个值丢失。
以你的代码为例:
def lazy_numbers(start, end):
while start < end:
yield start # ① 把当前 start 返回给调用者
start += 1 # ② 更新 start,下一轮会返回新值
如果 start=0 end=3:
| 循环 | yield 返回值 | 更新后 start |
|---|---|---|
| 第1次 | 0 | 1 |
| 第2次 | 1 | 2 |
| 第3次 | 2 | 3 -> while 结束 |
生成:0, 1, 2
行为完全符合 range(start, end)。
改成这样:
def lazy_numbers(start, end):
while start < end:
start += 1
yield start
再看流程:
start=0, end=3
| 循环 | start+=1 后 | yield 返回值 |
|---|---|---|
| 第1次 | 1 | 1 |
| 第2次 | 2 | 2 |
| 第3次 | 3 | while 终止(不 yield) |
结果:1, 2
- 👉 0 被跳过了
- 👉 3 没有被 yield
这显然不符合预期。
真正模拟 range 的写法必须是:
- 第一次产出 start 本身
- 然后再增加 start
否则行为无法与 range 对齐。
yield 类似:
“把这个值给你,等你下一次来拿的时候,我再更新状态。”
所以必须:
- 先给当前值 (yield start)
- 再准备下一次的值 (start += 1)
如果顺序反了:
“我先更新再给你”,那第一次就错了。