Python 生成器详解
前言
生成器是 Python 中非常有用的一种迭代器,其核心特性是实现惰性计算(Lazy Evaluation)和节省内存。与列表等容器不同,生成器不会一次性将所有数据加载到内存中,而是按需生成数据。本文将深入讲解生成器的定义方式、核心机制、高级用法及注意事项,并附有实用的示例代码。
本文详细讲解了 Python 生成器的定义、实现方式及使用场景。内容涵盖通过函数、类和表达式三种定义生成器的方法,深入分析了惰性计算与内存节省的原理。文章进一步介绍了 send、throw、close 等进阶控制方法,并通过代码示例展示了生成器在协程调度中的应用。最后总结了使用生成器时的注意事项,包括资源释放、状态保持及性能权衡,帮助开发者高效利用生成器优化程序结构。

生成器是 Python 中非常有用的一种迭代器,其核心特性是实现惰性计算(Lazy Evaluation)和节省内存。与列表等容器不同,生成器不会一次性将所有数据加载到内存中,而是按需生成数据。本文将深入讲解生成器的定义方式、核心机制、高级用法及注意事项,并附有实用的示例代码。
生成器是一种能够实现惰性计算、延迟执行和节省内存的迭代器。在 Python 中,通过 yield 语句实现生成器。
生成器中的函数不是直接返回一个值,而是返回一个生成器对象。取值时,通过 next() 方法或 for 循环操作获取生成器对象中的值。当生成器函数被调用时,它并不会立即执行函数体,而是返回一个生成器对象。只有当调用 next() 或进行迭代时,函数体才会开始执行,直到遇到 yield 语句暂停,保存当前状态,下次调用时从暂停处继续。
迭代器(Iterator)是实现了迭代器协议的对象,即实现了 __iter__() 和 __next__() 方法。生成器是迭代器的一种特殊形式,它自动实现了这两个方法。所有的生成器都是迭代器,但并非所有的迭代器都是生成器。
生成器特别适合处理大数据量和耗时操作的场景,例如遍历文件或网络数据流、CPU 密集型计算、图像处理等。
生成器可以使用多种方式进行定义,包括通过函数、生成器类以及生成器表达式等。
使用函数实现生成器是一种常见的方式,只需在函数中使用 yield 关键字即可。
def fib(max_count):
a, b = 0, 1
count = 0
while count < max_count:
yield a
a, b = b, a + b
count += 1
# 迭代输出前 10 个斐波那契数
for n in fib(10):
print(n, end=" ")
其中,使用 yield 语句实现惰性计算,返回一个生成器对象。在 for 循环中迭代生成器对象输出结果。
使用生成器类可以实现更为复杂的生成器逻辑,例如在遍历数据库查询结果时,需要维护状态并返回多个字段。
class QueryResult:
def __init__(self):
self.items = []
self.index = 0
def add(self, item):
self.items.append(item)
def query(self):
while True:
if self.index >= len(self.items):
raise StopIteration
result = self.items[self.index]
self.index += 1
yield result
# 使用示例
res = QueryResult()
res.add("item1")
res.add("item2")
for item in res.query():
print(item)
上述案例中,QueryResult 类包含了一个 items 列表和一个索引 index,通过 query() 方法实现返回一个生成器对象。在 add() 方法中添加新的元素,query() 方法中使用 yield 语句实现惰性计算和延迟执行。
生成器表达式是一种简洁、方便的生成器定义方式,其语法类似于列表推导式,但使用圆括号 () 而不是方括号 []。
# 生成器表达式
gen = (i ** 2 for i in range(1, 10))
for n in gen:
print(n, end=" ")
# 对比列表推导式
list_comp = [i ** 2 for i in range(1, 10)]
print(len(list_comp)) # 会占用内存存储所有平方值
print(gen) # 打印的是生成器对象地址
其中,使用生成器表达式定义了一个生成器对象 gen,包含 1~9 的平方值,并迭代输出结果。相比列表推导式,生成器表达式在创建时不占用大量内存。
除了基础的 next() 调用,生成器还支持更高级的控制方法,如 send()、throw() 和 close()。
send(value) 方法可以向生成器发送一个值,该值会成为 yield 表达式的返回值。如果生成器没有准备好接收值(例如刚启动),则必须传入 None。
def counter(start=0):
n = yield start
while True:
n += 1
yield n
c = counter()
print(next(c)) # 启动生成器,返回初始值 0
print(c.send(5)) # 发送 5,yield 表达式返回 5,计数器变为 6
throw(type[, value[, traceback]]) 方法用于向生成器内部抛出一个异常。这可以用于在生成器内部捕获异常并进行处理。
def generator_with_error_handling():
try:
yield 1
yield 2
except ValueError as e:
print(f"Caught exception: {e}")
yield 3
g = generator_with_error_handling()
print(next(g)) # 输出 1
try:
g.throw(ValueError("Test Error"))
except StopIteration:
pass
close() 方法用于关闭生成器,触发 GeneratorExit 异常。这对于释放资源非常重要,特别是在生成器持有文件句柄或其他系统资源时。
def file_generator(filename):
f = open(filename, 'r')
try:
for line in f:
yield line
finally:
f.close()
print("File closed.")
# 模拟提前结束
import sys
if __name__ == "__main__":
# 注意:实际使用时请确保有对应文件
# g = file_generator('test.txt')
# next(g)
# g.close()
pass
生成器的 yield 语句实现了协程的特性,它取决于 next() 方法来驱动生成器函数的执行。下面是一个用生成器实现简单异步操作和协程调度的案例。
import time
def task1():
while True:
print("task 1")
yield
time.sleep(1)
def task2():
while True:
print("task 2")
yield
time.sleep(1)
def main():
t1 = task1()
t2 = task2()
while True:
next(t1)
next(t2)
if __name__ == "__main__":
# main()
pass
在上述代码中,我们定义了两个任务函数 task1 和 task2,它们都采用了生成器的形式。每个生成器循环调用时,打印当前任务的名称,然后暂停 1 秒钟。在 main() 函数中,我们通过创建两个任务的生成器 t1 和 t2,依次轮流执行它们,来模拟异步操作和协程调度。
运行上述程序后可以看到,输出结果会交替显示 'task 1' 和 'task 2',说明两个任务在进行协程调度,而且不会阻塞程序的执行。
需要注意的是,生成器的 yield 语句实现了协程的特性,它取决于 next() 方法来驱动生成器函数的执行。因此,在 main() 函数中先执行 t1 的 next() 方法,再执行 t2 的 next() 方法,反复循环调度任务。
当然,也可以使用 asyncio 库等更高级的工具来实现异步操作和协程调度,但在理解底层机制时,生成器提供了很好的基础。
Python 生成器是一种非常实用的编程技巧,可以实现惰性计算、延迟执行和节省内存等特性。但使用时需要注意以下几点:
yield 语句实现惰性计算。对于一些需要及时释放资源的操作(如打开的文件、网络连接),应在代码中显式调用 close() 方法,或者使用 try...finally 块确保资源被释放。yield 语句中的表达式计算结果不会直接返回,而是暂停函数的执行,并返回生成器对象,等待下个 next() 方法或 for 循环的调用。这意味着生成器内部的状态(局部变量)会在暂停期间保持不变。__next__() 和 __iter__() 方法。可以通过使用 __iter__() 方法返回自身实例来实现无限迭代器。通过准确地使用生成器,可以极大地提高程序的效率和可读性。掌握生成器的核心机制,有助于编写更高效、更优雅的 Python 代码。
生成器是 Python 语言中处理数据流的重要工具。通过理解其定义方式、核心机制以及高级控制方法,开发者可以在内存受限或需要处理大规模数据的场景中做出更优的选择。无论是简单的序列生成,还是复杂的协程调度,生成器都提供了强大的支持。在实际开发中,应结合具体需求,合理选择使用生成器或普通迭代器,以达到最佳的性能和可维护性。

微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 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