在 Python 中,yield 是一个重要的关键字,它与生成器(Generator)和懒惰计算(Lazy Evaluation)密切相关。理解 yield 对于编写高效、内存友好的 Python 代码至关重要。
yield 允许函数在迭代过程中产生值,而不必一次性将所有值计算出来。这种特性在处理大数据集、无限序列或需要流式处理数据时尤其有用。
本文详细讲解了 Python 中 yield 关键字的核心概念及其与生成器的关系。内容涵盖生成器函数的基本工作原理、生成器表达式的使用、状态保存机制以及懒惰计算的优势。文章进一步探讨了 send 方法向生成器发送数据、异常处理与资源清理、以及生成器与列表在内存占用上的性能对比。通过多个代码示例,展示了如何利用 yield 构建无限序列、数据过滤器及高效的数据处理管道,帮助开发者优化代码性能并减少内存消耗。

在 Python 中,yield 是一个重要的关键字,它与生成器(Generator)和懒惰计算(Lazy Evaluation)密切相关。理解 yield 对于编写高效、内存友好的 Python 代码至关重要。
yield 允许函数在迭代过程中产生值,而不必一次性将所有值计算出来。这种特性在处理大数据集、无限序列或需要流式处理数据时尤其有用。
yieldyield 的基本概念yield 用于定义生成器函数。与普通函数不同,生成器函数在被调用时不会立即执行函数体,而是返回一个生成器对象。当对生成器对象进行迭代(如调用 next() 或在 for 循环中)时,函数体才会开始执行。
当生成器函数执行到 yield 语句时,它会暂停函数的执行,将 yield 后面的表达式结果作为返回值,并保存当前的局部变量和执行状态。当下一次请求值时,函数会从上次暂停的地方继续执行,直到遇到下一个 yield 或函数结束。
生成器是一种特殊类型的迭代器,由生成器函数创建。生成器函数包含至少一个 yield 语句。这允许生成器函数的状态保持不变,而值可以逐个生成。
以下是一个简单的生成器函数示例:
def simple_generator():
yield 1
yield 2
yield 3
gen = simple_generator()
print(next(gen)) # 输出:1
print(next(gen)) # 输出:2
print(next(gen)) # 输出:3
示例中,simple_generator 是一个生成器函数。当我们创建生成器对象 gen 并调用 next() 函数时,生成器函数在每次调用后从 yield 语句处继续执行,并生成相应的值。如果再次调用 next() 且没有更多 yield,则会抛出 StopIteration 异常。
生成器函数是一种包含 yield 语句的函数,用于生成值。生成器函数的执行可以被多次暂停和继续,每次暂停都会生成一个值。
以下是一个生成器函数的示例,用于生成斐波那契数列:
def fibonacci_generator():
a, b = 0, 1
while True:
yield a
a, b = b, a + b
gen = fibonacci_generator()
for _ in range(10):
print(next(gen)) # 输出前 10 个斐波那契数
除了生成器函数,Python 还提供了生成器表达式,它类似于列表推导式,但是返回一个生成器对象,逐个生成值。生成器表达式的语法更紧凑,且不会一次性占用大量内存。
以下是一个生成器表达式的示例,用于生成自然数的平方:
gen = (x**2 for x in range(1, 6))
for value in gen:
print(value) # 输出:1 4 9 16 25
生成器表达式可以在不创建额外的函数的情况下生成值,适用于简单的迭代需求。
yield 的高级用法生成器函数在每次执行时都会保持其状态。这意味着它可以用于生成无限序列或大数据集,而不必将所有数据存储在内存中。
以下是一个无限递增的生成器示例:
def infinite_increment():
num = 0
while True:
yield num
num += 1
gen = infinite_increment()
for _ in range(5):
print(next(gen)) # 输出:0 1 2 3 4
yield 可以与条件结合使用,用于过滤生成的值。这允许生成器仅生成符合特定条件的值。
以下是一个示例,生成偶数的生成器:
def even_numbers():
num = 0
while True:
if num % 2 == 0:
yield num
num += 1
gen = even_numbers()
for _ in range(5):
print(next(gen)) # 输出:0 2 4 6 8
生成器的懒惰计算是一种在需要时计算值的方式,而不是一次性计算所有值。这在处理大型数据集或无限序列时非常有用。
以下是一个示例,生成自然数的平方,但只计算前 5 个:
def lazy_square(limit):
for x in range(1, limit + 1):
yield x**2
gen = lazy_square(5)
for value in gen:
print(value) # 输出:1 4 9 16 25
懒惰计算允许在处理大量数据时节省内存和计算资源。
send)生成器不仅可以产出值,还可以接收外部传入的值。通过 send() 方法,可以向生成器内部发送数据,该数据会成为当前 yield 表达式的值。
def counter():
n = 0
while True:
value = yield n
if value is not None:
n = value
else:
n += 1
gen = counter()
print(next(gen)) # 初始化,必须用 next() 先启动
print(gen.send(10)) # 发送 10,n 变为 10,下一次 yield 10
print(next(gen)) # 输出 11
注意:第一次调用 send() 之前,通常需要先调用 next() 来启动生成器,否则会因为等待第一个 yield 而报错。
生成器可以使用 try...finally 块来确保清理操作被执行。当生成器被垃圾回收或被显式关闭时,finally 块中的代码会运行。
def resource_generator():
try:
yield 1
yield 2
finally:
print("资源已释放")
gen = resource_generator()
print(next(gen))
gen.close() # 触发 finally 块
使用列表存储大量数据会占用大量内存,因为所有元素都存储在内存中。而生成器是惰性计算的,只存储当前状态。
import sys
# 列表:占用大量内存
list_data = [i for i in range(1000000)]
print(f"列表占用内存:{sys.getsizeof(list_data)} bytes")
# 生成器:占用极少内存
gen_data = (i for i in range(1000000))
print(f"生成器占用内存:{sys.getsizeof(gen_data)} bytes")
通常情况下,生成器的内存占用远小于列表,特别是在处理百万级数据时优势明显。
yield 的高级用法包括生成器的状态保存,允许无限递增或递减的生成器。还可以与条件结合使用,用于过滤生成的值,仅生成符合特定条件的值。最重要的是,yield 支持懒惰计算,允许在需要时计算值,而不是一次性计算所有值,从而节省内存和计算资源。
此外,yield 还支持 send() 方法实现双向通信,以及配合 try...finally 进行资源管理。在处理大型数据集、无限序列或需要逐个生成值的情况下,yield 是一个强大的工具。通过深入理解 yield,可以更好地利用生成器和懒惰计算,提高代码的效率和可维护性。
在实际开发中,建议优先使用生成器表达式或生成器函数来处理大规模数据流,避免不必要的内存消耗。

微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
解析常见 curl 参数并生成 fetch、axios、PHP curl 或 Python requests 示例代码。 在线工具,curl 转代码在线工具,online
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online