跳至内容

Python 中的 yield 关键字:它是什么以及如何使用?

Python 中的 yield 关键字会将普通函数变为生成器,它按需产生一系列值,而不是一次性计算出所有结果。
已更新 2026年10月6日

使用 AI 探索

ChatGPTClaudePerplexity

Python 函数并不总是包含 return 语句。生成器函数是指使用 yield 关键字而不是 return 的函数。

这类函数会产生生成器迭代器,它们是表示数据流的对象。迭代器所代表的元素只有在需要时才会被创建并产出。这种求值方式通常称为惰性求值(lazy evaluation)。

在处理大型数据集时,生成器提供了一种内存高效的替代方案,避免将数据存储在列表、元组以及其他数据结构中(这些结构需要为每个元素占用内存空间)。生成器函数还可以创建无限迭代器,而像列表和元组这类及早求值的结构无法做到这一点。

在开始之前,让我们回顾一下函数与生成器之间的差异:

特性

函数

生成器

值的产生

一次性返回所有值

按需一次产出一个值

执行方式

在返回前完整执行

每次产出后暂停,下一次请求值时再恢复

关键字

return

yield

内存使用

可能较高,需要在内存中存储整个序列

较低,仅保存当前值和下一步的状态

迭代

可多次迭代,但需要存储整个序列

为单次遍历设计,对大型或无限序列更高效

使用 Python 的 yield 创建生成器函数

在 Python 中,generator 既可以指生成器迭代器,也可以指生成器函数。它们在 Python 中是不同但相关的对象。为避免混淆,本教程经常使用完整术语。

我们先来探索生成器函数。生成器函数看起来与常规函数类似,但它包含 yield 关键字而不是 return。

当 Python 程序调用生成器函数时,它会创建一个生成器迭代器。迭代器按需产出一个值,并在需要下一个值之前暂停执行。我们来看一个示例,以解释这一概念,并展示常规函数与生成器函数之间的差异。

使用常规函数

首先,定义一个包含 return 语句的常规函数。该函数接收一个单词序列和一个字母,返回一个列表,其中包含每个单词中该字母出现的次数:

def find_letter_occurrences(words, letter):
    output = []
    for word in words:
        output.append(word.count(letter))
    return output
print(
    find_letter_occurrences(["apple", "banana", "cherry"], "a")
)
[1, 3, 0]

该函数输出的列表为 1、3 和 0,因为 apple 中有一个 a,banana 中有三个 a,cherry 中没有 a。这个函数还可以重构为使用列表推导式,而不是初始化空列表并使用 .append():

def find_letter_occurrences(words, letter):
    return [word.count(letter) for word in words]

该常规函数在每次调用时都会返回包含所有结果的列表。然而,如果单词列表很大,调用这个常规函数会增加内存需求,因为程序会创建并存储一个与原列表同等大小的新列表。如果该函数在多个输入上被反复使用,或者类似的函数对原始数据执行其他操作,内存压力会迅速增加。

使用生成器函数

可以改用生成器函数:

def find_letter_occurrences(words, letter):
    for word in words:
        yield word.count(letter)
words = ["apple", "banana", "cherry"]
letter = "a"
output = find_letter_occurrences(words, letter)
print(output)
<generator object find_letter_occurrences at 0x102935e00>

该函数包含yield 关键字而非 return。这个生成器函数在被调用时返回一个生成器对象,并将其赋给 output。该对象是一个迭代器,它并不包含每个单词中字母出现次数的数据。相反,生成器会在需要时创建并产出这些值。让我们从这个生成器迭代器中取出第一个值:

print(next(output))
1

内置函数 next() 是从迭代器中获取下一个值的一种方式。我们稍后会介绍其他方式。

生成器函数中的代码会执行,直到程序运行到包含 yield 关键字的那一行。在此示例中,for 循环开始第一轮迭代,获取列表 words 的第一个元素。字符串方法 .count() 返回一个整数,本例中为 1,因为 apple 中有一个 a。生成器产出该值,next(output) 返回这一值。

此时生成器 output 会暂停执行。因此,生成器完成了 for 循环的第一次迭代,并找出了列表首个单词中字母 a 的出现次数。它现在在等待下一次被需要。

如果再次以 output 作为参数调用内置的 next(),生成器会从暂停处恢复执行:

print(next(output))
3

生成器从 for 循环第一次迭代中包含 yield 的那一行继续。由于 for 循环中没有更多代码,它会回到循环顶部,获取列表 words 的第二个元素。本例中 .count() 返回 3,并产出该值。生成器再次在此处暂停。

第三次调用 next() 会恢复执行:

print(next(output))
0

第二次迭代到达 for 循环的末尾,然后进入第三次迭代。代码再次运行到包含 yield 的那一行,这次产出整数 0,因为 cherry 中没有 a。

生成器再次暂停。只有当我们第四次调用 next() 时,程序才会决定该生成器的命运:

print(next(output))
Traceback (most recent call last):
  ...
StopIteration

执行从 for 循环第三次迭代的尾部恢复。然而,循环已完成遍历,因为列表 words 中不再有元素。生成器会抛出 StopIteration 异常。

在大多数用例中,生成器的元素并不是通过直接调用 next() 来获取,而是通过其他迭代过程来访问。StopIteration 异常会发出迭代结束的信号。我们将在本教程下一部分进一步探讨。

当生成器的操作可以用一个表达式表示时,Python 还有另一种创建生成器迭代器的方式,就像前面的示例一样。生成器迭代器 output 可以使用生成器表达式来创建:

words = ["apple", "banana", "cherry"]
letter = "a"
output = (word.count(letter) for word in words)
print(next(output))
print(next(output))
print(next(output))
print(next(output))
1
3
0
Traceback (most recent call last):
  ...
StopIteration

赋给 output 的圆括号中的表达式是生成器表达式,它创建了一个与生成器函数 find_letter_occurrences() 所产生的迭代器类似的生成器迭代器。

我们用另一个生成器函数的示例来结束本节,以突出每次需要元素时执行如何暂停与恢复:

def show_status():
    print("Start")
    yield
    print("Middle")
    yield
    print("End")
    yield
status = show_status()
next(status)
Start

这个生成器函数没有循环。相反,它包含三行带有 yield 关键字的代码。调用生成器函数 show_status() 会创建生成器迭代器 status。程序第一次调用 next(status) 时,生成器开始执行,打印字符串 "Start",并在第一个 yield 表达式后暂停。由于 yield 关键字后没有对象,生成器会产出 None。

只有在第二次调用 next() 时,程序才会打印字符串 "Middle":

next(status)
Middle

生成器在第二个 yield 表达式后暂停。第三次调用 next() 会打印最后一个字符串 "End":

next(status)
End

生成器在最后一个 yield 表达式处暂停。下一次程序从该生成器迭代器请求值时,它会抛出 StopIteration 异常:

next(status)
Traceback (most recent call last):
  ...
StopIteration

我们将在下一节探索更多使用生成器的方法。

使用生成器迭代器

生成器函数会创建生成器迭代器,而迭代器是可迭代的。我们来拆解这句话。每次程序调用生成器函数,都会创建一个迭代器。由于迭代器是可迭代对象,它们可以用于 for 循环和其他迭代过程。

因此,内置函数 next() 并不是访问迭代器元素的唯一方式。本节将探索与生成器配合使用的其他方式。

结合 Python 的迭代协议使用生成器迭代器

让我们回顾一下本教程前面部分的一个生成器函数:

def find_letter_occurrences(words, letter):
    for word in words:
        yield word.count(letter)
words = ["apple", "banana", "cherry"]
letter = "a"
output = find_letter_occurrences(words, letter)
for value in output:
    print(value)
1
3
0

这段代码没有多次使用 next(),而是在 for 循环中使用生成器迭代器 output。由于迭代器是可迭代对象,它们可以放入 for 循环。该循环会从生成器迭代器中取出项目,直到不再有剩余值。

与列表和元组等数据结构不同,迭代器只能使用一次。如果我们尝试第二次运行相同的 for 循环,代码不会再次打印出这些值:

def find_letter_occurrences(words, letter):
    for word in words:
        yield word.count(letter)
words = ["apple", "banana", "cherry"]
letter = "a"
output = find_letter_occurrences(words, letter)
print("First attempt:")
for value in output:
    print(value)
print("Second attempt:")
for value in output:
    print(value)
First attempt:
1
3
0
Second attempt:

第一次 for 循环已经耗尽了该迭代器,因此它不再能产出任何值。如果在迭代器耗尽后还需要再次使用生成器,我们必须通过生成器函数再创建一个新的生成器迭代器。

同一程序中也可以同时存在多个生成器迭代器:

 
def find_letter_occurrences(words, letter):
    for word in words:
        yield word.count(letter)
words = ["apple", "banana", "cherry"]
letter = "a"
first_output = find_letter_occurrences(words, letter)
second_output = find_letter_occurrences(words, letter)
print("First value of first_output:")
print(next(first_output))
print("Values of second_output:")
for value in second_output:
    print(value)
print("Remaining values of first_output:")
for value in first_output:
    print(value)
First value of first_output:
1
Values of second_output:
1
3
0
Remaining values of first_output:
3
0

生成器函数 find_letter_occurrences() 创建了两个生成器迭代器:first_output 和 second_output。尽管两个迭代器都引用列表 words 中相同的数据,但它们彼此独立推进。

该示例使用 next() 从 first_output 中获取第一个值。生成器迭代器产出 1,并在此处暂停。接下来,程序遍历 second_output。由于该生成器尚未产出任何值,循环会遍历第二个迭代器产出的所有值。最后,另一个 for 循环遍历 first_output。不过,该迭代器先前已经产出了第一个值,循环只会遍历该迭代器中剩余的值。

for 循环并不是遍历生成器迭代器的唯一过程:

print(*find_letter_occurrences(words, letter))
print(sorted(find_letter_occurrences(words, letter)))
1 3 0
[0, 1, 3]

在这些示例中,程序直接调用生成器函数来创建并使用生成器迭代器,而不是先将其赋给变量。第一个示例使用星号解包迭代器。这个过程依赖与 for 循环相同的迭代协议。

第二个示例将生成器迭代器传给内置的 sorted(),它需要一个可迭代对象作为参数。生成器是可迭代对象,因此在发生 Python 迭代的场景中都可以使用它们。

创建无限迭代器

生成器会产出一个值并暂停,直到需要下一个值。每次代码从迭代器请求一个值,生成器函数内部的代码都会执行,直到下一个 yield 表达式被求值。到目前为止,本教程中的示例都只有有限个 yield 表达式。然而,通过在生成器函数中使用 while 循环,可以创建一个产出无限多个值的生成器。以下示例中,生成器会从传入的颜色列表中随机产出颜色:

import random
def get_color(colors):
    while True:
        yield random.choice(colors)
output_colors = get_color(["red", "green", "blue"])
print("First two colors:")
print(next(output_colors))
print(next(output_colors))
print("Next 10 colors using a 'for' loop:")
for _ in range(10):
    print(next(output_colors))
First two colors:
green
red
Next 10 colors using a 'for' loop:
blue
green
green
green
red
red
red
blue
green
red

生成器函数 get_color() 在 while 循环中包含一个 yield 表达式。因此,每当需要下一个值时,代码总会遇到另一个 yield 表达式。生成器迭代器 output_colors 会从输入列表中随机选择并无限产出颜色。这个生成器永远不会被耗尽。

无法创建无限的列表或元组等数据结构。生成器使程序能够创建无限可迭代对象。请注意,如果在 for 循环中直接使用该生成器迭代器,循环将永远运行下去。

进阶生成器概念

在 Python 中,生成器还有更高级的用法。本节将探索其中的一些。

向生成器发送对象

生成器还可以接收在求值过程中使用的附加数据。包含 yield 关键字的语句是一个表达式,会被求值为某个值。这个值可以在生成器函数内部赋给变量。我们先用一个基础示例来演示这一概念:

def generator_function():
    value = yield 1
    print(f"The yield expression evaluates to: {value}")
    value = yield 2
    print(f"The yield expression evaluates to: {value}")
output = generator_function()
print(next(output))
print(next(output))
print(next(output))
1
The yield expression evaluates to: None
2
The yield expression evaluates to: None
Traceback (most recent call last):
  ...
StopIteration

Python 的 yield 关键字会创建一个求值为某个值的表达式。不过,该表达式在生成器函数内部的取值并不是生成器产出的那个对象。考虑第一次 yield 表达式。生成器产出整数 1。因此,第一次调用 print(next(output)) 会显示 1,并使生成器暂停执行。

然而,生成器内部的 yield 表达式会被求值为一个对象,并将该对象赋给变量名 value。在这个示例中,yield 将 None 赋值给 value。对于生成器函数中的第二个 yield 也会重复这一过程。第三次调用 next() 的目的是确保生成器函数中的所有代码都被执行。

让我们将第二次和第三次对 next() 的调用替换为 .send(),这是生成器类中的一个方法:

def generator_function():
    value = yield 1
    print(f"The yield expression evaluates to: {value}")
    value = yield 2
    print(f"The yield expression evaluates to: {value}")
output = generator_function()
print(next(output))
print(output.send("Here's a value"))
print(output.send("Here's another value"))
1
The yield expression evaluates to: Here's a value
2
The yield expression evaluates to: Here's another value
Traceback (most recent call last):
  ...
StopIteration

生成器函数保持不变。通过调用 next() 启动生成器,代码运行直到第一次产出整数 1。第二次不再使用 next(),程序改为调用 output.send()。该方法会向生成器发送一个对象。本例中对象是字符串。生成器函数中的 yield 表达式会被求值为这个字符串,并赋给 value。因此,生成器可以在其代码中使用该字符串。

第二次调用 .send() 会向生成器发送一个新对象,并同样赋给变量 value。由于没有更多的 yield 表达式,最后一次 print() 调用后生成器会抛出 StopIteration。

我们再看一个使用 .send() 的示例。下面的生成器会显示账户余额,同时余额可以被更新:

def get_balance(start_balance):
    balance = start_balance
    while True:
        amount = yield balance
        if amount is not None:
            balance += amount
current_balance = get_balance(100)
print(next(current_balance))
print(current_balance.send(10))
print(current_balance.send(-20))
print(next(current_balance))
100
110
90
90

该生成器函数在调用时需要一个起始余额。balance 的值会在生成器执行过程中发生变化。任何通过 .send() 发送到生成器的对象,都会赋给 amount。如果生成器在没有接收任何发送对象的情况下产出一个值,则该变量为 None;否则它将包含通过 .send() 发送的对象。

生成器迭代器 current_balance 以 100 美元的余额开始。通过调用 next() 启动生成器,代码开始执行,直到第一次产出值。

一旦生成器启动,就可以使用 .send() 而不是 next() 来重新开始执行。生成器会将发送的值累加到余额中。如果没有发送任何值,例如再次调用 next(),生成器将产出未变化的余额。

直接从另一个可迭代对象中产出

Python 生成器还可以使用 yield from 语法,直接从另一个生成器或可迭代对象中产出值。我们来看一个从嵌套列表中产出值的生成器函数示例:

def flatten(nested_list):
    for item in nested_list:
        if isinstance(item, list):
            yield from flatten(item)
        else:
            yield item
nested_list = [1, [2, 3], [4, [5, 6]], 7]
print(list(flatten(nested_list)))
[1, 2, 3, 4, 5, 6, 7]

该生成器函数接收一个列表,该列表中可以包含嵌套列表。for 循环遍历列表中的各个项目。外层列表中的每个项目要么是一个值(本例为整数),要么是另一个列表。当项目不是列表时,生成器直接产出该项目。

然而,当项目是列表时,生成器会使用内层列表作为参数递归调用生成器函数 flatten()。这会创建另一个以该内层列表为数据源的生成器迭代器。如果这里使用 yield 表达式,第一个生成器会产出第二个生成器本身。而通过使用 yield from,第一个生成器会直接产出来自第二个生成器的值。

总结:yield vs. return

包含 return 与 yield 的函数定义看起来相似,但其行为不同。让我们总结关键差异:

 

常规函数

生成器函数

关键字

return(若未显式使用则为隐式返回)

yield

调用时

执行代码直到遇到 return,然后返回最终值

创建一个生成器迭代器

终止方式

由 return 语句终止

由 yield 暂停,可在稍后恢复

返回值

单个对象(可为数据结构)

生成器迭代器

yield 表达式

不适用(创建的是语句)

求值为 None 或通过 .send() 发送的值

使用场景

适合返回最终结果

适合创建数据流,尤其是大型或无限序列

结语

Python 的 yield 关键字用于在函数中定义生成器函数。这些函数在被调用时会创建生成器迭代器。生成器是 Python 中惰性求值的一个例子:表达式在需要其值时才被求值,而不是在执行表达式时立即求值。因此,yield 表达式有助于创建数据流,使值能够按需生成而无需将其存储在内存中。

在需要进行大量操作的大型数据集场景中,效率至关重要。Python 的生成器迭代器是高效处理海量数据的核心工具之一。

如果您想进一步学习 Python,请查看这个Python Developer 职业路径。


Stephen Gruppetta's photo
Author
Stephen Gruppetta
LinkedIn
Twitter

我在马耳他大学完成了本科阶段的物理与数学学习。随后前往伦敦,并在帝国理工学院取得物理学博士学位,研究用于成像人类视网膜的新型光学技术。现在,我专注于撰写与传播 Python 相关内容,并教授 Python。

主题
Python

用这些课程学习 Python!

课程

Python for Developers 入门

3 小时
185.5K
掌握 Python 编程基础。 无需任何先验知识!
查看详情Right Arrow
开始课程
查看更多Right Arrow