Skip to main content
Documents
Toggle Dark/Light/Auto mode Toggle Dark/Light/Auto mode Toggle Dark/Light/Auto mode Back to homepage

Yield 函数

以下是一份 最清晰、最实用、最容易理解的 yield 讲解,带超多示例,一看就会。

✅ yield 是什么?

yield 是 Python 用来创建 生成器(generator)函数 的关键字。

带有 yield 的函数不会一次性运行完毕,而是一次返回一个值,每次暂停在 yield 这里,下次继续运行。

是 “可以暂停的 return”。

🔥 一句话记住 yield

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?

普通函数:

  • 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

可生成无限序列!

五、yield from(委托生成器)

让生成器“嵌套”更优雅:

def gen1():
    yield from [1, 2, 3]
    yield from [4, 5]

print(list(gen1()))

输出:

[1, 2, 3, 4, 5]

六、yield + send(双向通信)

生成器不仅能返回值,还能接收值:

def echo():
    while True:
        x = yield
        print("Got:", x)

g = echo()
next(g)            # 启动生成器
g.send("hello")    # Got: hello
g.send("world")    # Got: world

七、yield 的典型使用场景

场景 为什么用 yield
读取大文件/流式数据 不占太多内存
无限序列 不需要一次性返回
管道式数据处理(类似 Linux pipe) 优雅、可组合
生成器表达式 更节省内存
异步编程(历史上 asyncio) yield from/await 的前身

八、yield 的本质(高级但清晰)

yield 会让函数变成一个 状态机:

  • 执行到 yield 时 -> 返回值给调用者 -> 暂停函数
  • 下次调用 next() -> 恢复执行 -> 从 yield 后继续运行

你可以理解成 “返回值 + 暂停”。

九、for 循环为什么能用在生成器上?

因为生成器实现了迭代协议:

  • __iter__
  • __next__

for 自动调用 next()。

🔥 超实用模板

1)遍历生成数据

def generate_items():
    for i in range(10):
        yield i

2)过滤数据(类似 filter)

def even_numbers(nums):
    for n in nums:
        if n % 2 == 0:
            yield n

3)组合生成器(yield from)

def combined():
    yield from gen_a()
    yield from gen_b()

🎯 终极总结

  • yield 让函数变成生成器
  • 生成器“按需生成”值
  • 执行到 yield 暂停,下次继续
  • 节省内存、适合大数据流
  • 可无限序列
  • 适合管道式编程

Next

遍历生成数据

def generate_items():
    for i in range(10):
        yield i

生成的数据在哪里?如何获取?

yield 生成的数据 不会自动存起来,它是惰性生成的,你必须主动“取”出来。

你可以把它理解为:

👉 生成器不会一次性返回所有数据,而是每次取一个。

✅ 你的代码

def generate_items():
    for i in range(10):
        yield i

这是一个生成器函数,调用它后不会执行函数,而是返回一个 生成器对象。

✅ 1. 如何获取数据?(4 种常用方式)

方式 1:for 遍历(最常用)

for x in generate_items():
    print(x)

输出:

0
1
2
...
9

方式 2:转换成 list(一次性取完)

items = list(generate_items())
print(items)

输出:

[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

方式 3:使用 next() 单步获取

gen = generate_items()

print(next(gen))  # 0
print(next(gen))  # 1
print(next(gen))  # 2

直到耗尽会抛 StopIteration。

方式 4:解包(不常用但优雅)

a, b, c, *rest = generate_items()
print(a, b, c, rest)

输出:

0 1 2 [3,4,5,6,7,8,9]

✅ 2. 生成器是什么?

调用 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())

start += 1 为什么要放在 yield start 之后?

代码

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)。

❌ 如果 start += 1 放在 yield 前面?

改成这样:

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

这显然不符合预期。

📌 和 for i in range(start, end) 一样的逻辑

真正模拟 range 的写法必须是:

  • 第一次产出 start 本身
  • 然后再增加 start

否则行为无法与 range 对齐。

🎯 最通俗的解释

yield 类似:

“把这个值给你,等你下一次来拿的时候,我再更新状态。”

所以必须:

  1. 先给当前值 (yield start)
  2. 再准备下一次的值 (start += 1)

如果顺序反了:

“我先更新再给你”,那第一次就错了。