Python 生成器与 yield:工作原理及示例详解
Python 语言以其简洁、优雅和强大的特性吸引了全球无数开发者。在 Python 的众多高级特性中,生成器(Generators)无疑是一个强大而优雅的工具,它通过巧妙地利用 yield
关键字,为处理大量数据、实现无限序列以及构建高效数据管道提供了全新的思路。
本文将带您深入理解 Python 生成器和 yield
的核心概念、工作原理,并通过丰富的示例展示它们的实际应用和巨大优势。
1. 引言:为什么需要生成器?
在 Python 中,当我们处理集合数据时,通常会想到列表(list)、元组(tuple)或集合(set)。这些数据结构非常方便,但它们的一个共同特点是:它们是 一次性加载 的。这意味着当您创建一个包含大量元素的列表时,所有元素都会被计算并存储在内存中。
考虑以下场景:
- 您需要读取一个几十 GB 甚至上 TB 的巨大日志文件。
- 您需要处理一个包含数亿条记录的数据库查询结果。
- 您需要生成一个理论上无限的序列(例如,所有自然数、斐波那契数列)。
在这种情况下,尝试将所有数据一次性加载到内存中是不可行的,或者说,会导致严重的内存溢出(MemoryError),甚至使整个系统崩溃。传统的列表或元组等数据结构在这种场景下显得力不从心。
此外,有时候我们只需要对数据进行 惰性计算(Lazy Evaluation),即只在需要时才计算和获取下一个数据,而不是一次性计算所有数据。这不仅节省内存,还能提高程序的响应速度,特别是在数据处理流程的早期阶段。
正是为了解决这些问题,Python 引入了生成器。生成器提供了一种方式,让您可以按需(on-demand)生成序列中的下一个元素,而无需在内存中存储整个序列。
2. 理解迭代器(Iterators)与可迭代对象(Iterables)
在深入生成器之前,我们有必要先理解与之紧密相关的两个概念:可迭代对象(Iterable)和迭代器(Iterator)。生成器本质上是一种特殊的迭代器。
-
可迭代对象 (Iterable): 任何可以使用
for
循环遍历的对象都是可迭代对象。例如,列表、元组、字符串、字典、集合以及文件对象等都是可迭代对象。可迭代对象的特点是实现了__iter__()
方法。调用__iter__()
方法会返回一个 迭代器。 -
迭代器 (Iterator): 迭代器是一个表示数据流的对象。它实现了迭代器协议,即包含
__iter__()
和__next__()
方法。__iter__()
方法通常返回迭代器本身。__next__()
方法返回序列中的下一个元素。如果没有更多元素可返回,则会抛出StopIteration
异常。
当我们使用 for
循环遍历一个可迭代对象时,Python 在后台做了以下事情:
- 调用可迭代对象的
__iter__()
方法,获取一个迭代器对象。 - 重复调用迭代器对象的
__next__()
方法来获取下一个元素。 - 当
__next__()
方法抛出StopIteration
异常时,for
循环捕获该异常并结束迭代。
示例:手动使用迭代器
“`python
my_list = [1, 2, 3, 4]
获取可迭代对象的迭代器
my_iterator = iter(my_list) # 实际上调用的是 my_list.iter()
手动迭代
print(next(my_iterator)) # 实际上调用的是 my_iterator.next()
print(next(my_iterator))
print(next(my_iterator))
print(next(my_iterator))
尝试获取下一个元素,会抛出 StopIteration 异常
try:
print(next(my_iterator))
except StopIteration:
print(“迭代结束,没有更多元素了。”)
for 循环的本质
print(“\n使用 for 循环遍历:”)
for item in my_list:
print(item)
“`
输出:
“`
1
2
3
4
迭代结束,没有更多元素了。
使用 for 循环遍历:
1
2
3
4
“`
手动编写一个实现迭代器协议的类可能会比较繁琐,需要维护状态(当前位置)、处理异常等。生成器为我们提供了一种更简单、更优雅的方式来创建迭代器。
3. 认识 yield
关键字
yield
是生成器的核心。它看起来像 return
语句,但行为却完全不同。
return
: 当函数执行到return
语句时,函数会停止执行,并将指定的值返回给调用者。函数的局部变量和执行状态都会被销毁。yield
: 当函数执行到yield
语句时,函数会 暂停 执行,并将yield
后面的值作为生成器的下一个元素返回给调用者。与return
不同的是,当函数暂停时,它的局部变量和执行状态会 被保存。下次调用生成器的__next__()
方法时,函数会从上次暂停的地方 继续执行,直到遇到下一个yield
语句或函数结束。
关键区别总结:
特性 | return |
yield |
---|---|---|
执行行为 | 终止函数执行并返回值 | 暂停函数执行并返回值,保存状态 |
返回次数 | 通常一次(除非递归等特殊情况) | 可以多次 |
返回对象 | 函数返回的值 | 生成器的下一个元素 |
函数类型 | 普通函数 | 生成器函数 |
调用结果 | 返回具体计算结果 | 返回一个生成器对象 |
内存使用 | 可能一次性计算并返回所有结果 | 惰性计算,按需产生结果,节省内存 |
状态 | 不保存 | 保存函数执行状态、局部变量等 |
恢复执行 | 不支持 | 支持,从暂停处继续执行 |
任何包含 yield
语句的函数都被称为 生成器函数。调用生成器函数并不会立即执行函数体,而是会返回一个 生成器对象。这个生成器对象是一个迭代器,您可以对其调用 next()
或在 for
循环中使用它来驱动生成器函数的执行。
示例:return
与 yield
的对比
“`python
普通函数
def my_return_function():
print(“执行到第一步…”)
return 1
print(“执行到第二步…”) # 这行代码永远不会被执行
生成器函数
def my_yield_function():
print(“执行到第一步…”)
yield 1
print(“执行到第二步…”)
yield 2
print(“执行到第三步…”)
调用普通函数
print(“— 调用普通函数 —“)
result = my_return_function()
print(f”普通函数返回: {result}”)
print(next(result)) # 这会报错,因为 result 是一个整数,不是迭代器
print(“\n— 调用生成器函数 —“)
调用生成器函数,返回一个生成器对象
generator_obj = my_yield_function()
print(f”调用生成器函数返回: {generator_obj}”) # 打印的是生成器对象本身
驱动生成器执行,获取第一个 yield 的值
print(“获取第一个值:”)
first_value = next(generator_obj) # 执行到第一个 yield 暂停
print(f”第一个 yield 的值: {first_value}”)
驱动生成器执行,获取第二个 yield 的值
print(“\n获取第二个值:”)
second_value = next(generator_obj) # 从上次暂停处继续执行,直到第二个 yield 暂停
print(f”第二个 yield 的值: {second_value}”)
再次驱动生成器执行,函数执行完毕,没有更多 yield
print(“\n尝试获取第三个值:”)
try:
third_value = next(generator_obj) # 从上次暂停处继续执行,函数执行完毕,抛出 StopIteration
print(f”第三个 yield 的值: {third_value}”) # 这行不会被执行
except StopIteration:
print(“生成器执行完毕,抛出 StopIteration 异常。”)
“`
输出:
“`
— 调用普通函数 —
执行到第一步…
普通函数返回: 1
— 调用生成器函数 —
调用生成器函数返回:
获取第一个值:
执行到第一步…
第一个 yield 的值: 1
获取第二个值:
执行到第二步…
第二个 yield 的值: 2
尝试获取第三个值:
执行到第三步…
生成器执行完毕,抛出 StopIteration 异常。
“`
从上面的示例可以看出,my_yield_function
在每次 next()
调用时,会从上次 yield
的地方恢复执行,直到遇到下一个 yield
或函数结束。这正是生成器实现惰性计算和状态保存的关键。
4. 生成器的工作原理:幕后机制
当 Python 解释器遇到一个包含 yield
语句的函数时,它会将该函数标记为一个生成器函数。调用这个函数时,并不会像普通函数那样立即执行,而是创建一个特殊的 生成器对象。
这个生成器对象实现了迭代器协议 (__iter__()
和 __next__()
)。当您首次调用生成器对象的 __next__()
方法(或者使用 for
循环隐式调用时),生成器函数体开始执行,直到遇到第一个 yield
语句。此时,函数暂停执行,将 yield
后面的值作为结果返回,并将当前执行状态(包括局部变量的值、指令指针等)保存在生成器对象内部。
当再次调用生成器对象的 __next__()
方法时,生成器会从上次暂停的地方恢复执行,继续执行函数体,直到遇到下一个 yield
语句、return
语句(导致 StopIteration
)或者函数结束(也导致 StopIteration
)。这个过程会重复进行,直到函数完全执行完毕。
可以把生成器对象想象成一个带有“书签”的函数。每次调用 next()
,它就翻到书签所在页继续读,直到遇到下一个书签 (yield
),然后停下来,把书签移到那里,并把书页上的内容给你。下次你再叫它读,它就从新书签的位置开始。
这种按需生成的方式,使得生成器在内存使用上具有巨大的优势,尤其是在处理大型数据集时。它只需要在内存中保留当前正在处理的数据以及生成器的内部状态,而无需存储整个数据集。
5. 创建生成器的两种方式
有两种主要的方式在 Python 中创建生成器:
5.1 生成器函数 (Generator Functions)
这是最常见的方式,通过在函数定义中使用 yield
语句来创建。上面示例中的 my_yield_function
就是一个生成器函数。
“`python
一个生成器函数,用于生成一个范围内的平方数
def squares_generator(start, end):
print(f”开始生成从 {start} 到 {end} 的平方数…”)
for i in range(start, end + 1):
print(f”正在生成 {i} 的平方…”)
yield i * i
print(f”暂停,等待下一次调用…”)
print(“所有平方数已生成,生成器函数执行完毕。”)
创建生成器对象
sq_gen = squares_generator(1, 5)
print(“— 开始迭代 —“)
使用 for 循环迭代生成器
for square in sq_gen:
print(f”从生成器中获取的值: {square}”)
print(“-” * 10) # 分隔每次迭代
print(“— 迭代结束 —“)
尝试再次使用已耗尽的生成器(不会输出任何东西,也不会报错,因为 for 循环已经处理了 StopIteration)
print(“\n— 再次迭代已耗尽的生成器 —“)
for square in sq_gen:
print(f”再次获取值: {square}”) # 这行不会被执行
“`
输出:
“`
— 开始迭代 —
开始生成从 1 到 5 的平方数…
正在生成 1 的平方…
从生成器中获取的值: 1
暂停,等待下一次调用…
正在生成 2 的平方…
从生成器中获取的值: 4
暂停,等待下一次调用…
正在生成 3 的平方…
从生成器中获取的值: 9
暂停,等待下一次调用…
正在生成 4 的平方…
从生成器中获取的值: 16
暂停,等待下一次调用…
正在生成 5 的平方…
从生成器中获取的值: 25
暂停,等待下一次调用…
所有平方数已生成,生成器函数执行完毕。
— 迭代结束 —
— 再次迭代已耗尽的生成器 — # 没有输出,因为生成器已经抛出 StopIteration
“`
从输出可以看出,每次 yield
都会暂停函数,并在下一次迭代时恢复执行。
5.2 生成器表达式 (Generator Expressions)
生成器表达式是一种更简洁的创建生成器的方式,语法类似于列表推导式(List Comprehensions),但使用的是圆括号 ()
而不是方括号 []
。
列表推导式 [expr for item in iterable if condition]
会一次性构建并返回整个列表。
生成器表达式 (expr for item in iterable if condition)
会返回一个生成器对象,该对象可以按需生成元素。
“`python
列表推导式 (一次性生成所有平方数并存储在列表中)
list_of_squares = [x * x for x in range(1, 6)]
print(f”列表推导式结果: {list_of_squares}”)
print(f”列表推导式结果类型: {type(list_of_squares)}”)
print(f”列表推导式结果占用的内存大致(元素数量 * 每个元素大小)…”) # 实际内存复杂,这里简化说明
生成器表达式 (返回一个生成器对象)
generator_of_squares = (x * x for x in range(1, 6))
print(f”\n生成器表达式结果: {generator_of_squares}”)
print(f”生成器表达式结果类型: {type(generator_of_squares)}”)
print(f”生成器表达式本身占用的内存很小…”)
迭代生成器表达式获取值
print(“迭代生成器表达式:”)
for square in generator_of_squares:
print(square)
注意:生成器表达式只能迭代一次
print(“\n再次尝试迭代生成器表达式:”)
for square in generator_of_squares:
print(square) # 这行不会被执行
“`
输出:
“`
列表推导式结果: [1, 4, 9, 16, 25]
列表推导式结果类型:
列表推导式结果占用的内存大致(元素数量 * 每个元素大小)…
生成器表达式结果:
生成器表达式结果类型:
生成器表达式本身占用的内存很小…
迭代生成器表达式:
1
4
9
16
25
再次尝试迭代生成器表达式: # 没有输出
“`
生成器表达式非常适合在需要简单、一次性迭代的场景下使用,比如在函数调用时作为参数传递一个可迭代对象:
“`python
计算一个范围内的平方数的总和,不需要一次性生成所有平方数的列表
total_sum_of_squares = sum(x * x for x in range(1, 1000001)) # 使用生成器表达式
print(f”\n1到100万平方数的总和: {total_sum_of_squares}”)
如果使用列表推导式,可能会导致内存问题或效率低下
total_sum_of_squares_list = sum([x * x for x in range(1, 1000001)]) # 创建一个巨大的列表再求和
print(f”1到100万平方数的总和 (使用列表): {total_sum_of_squares_list}”)
“`
6. 生成器的主要优势
总结来说,使用生成器主要有以下几个显著优势:
- 内存效率 (Memory Efficiency): 这是生成器最大的优势。它们采用惰性求值,只在需要时生成下一个元素,而不是一次性将所有元素加载到内存中。这使得生成器非常适合处理大型数据集或无限序列。
- 处理无限序列 (Handling Infinite Sequences): 使用
while True
循环结合yield
可以轻松创建表示无限序列的生成器。由于是按需生成,即使序列无限,程序也不会耗尽内存。 - 提高性能和响应速度 (Improved Performance and Responsiveness): 对于大型数据集,生成器可以立即开始处理和产生第一个结果,而不需要等待整个数据集加载和处理完毕。这可以提高程序的启动速度和用户响应性。
- 管道化数据处理 (Pipelining Data Processing): 生成器可以很方便地链式调用,形成数据处理管道。一个生成器的输出可以作为另一个生成器的输入,从而实现复杂的数据转换和过滤流程,而无需创建中间的完整列表。
- 代码简洁性 (Code Simplicity): 与手动编写一个实现迭代器协议的类相比,生成器函数使用
yield
关键字,语法更简洁直观。生成器表达式则进一步简化了简单生成器的创建。
7. 高级生成器特性 (send()
, throw()
, close()
, yield from
)
除了简单的按需生成,生成器还支持更复杂的交互,允许调用者与生成器函数进行双向通信。
7.1 send(value)
:向生成器发送值
send(value)
方法不仅会驱动生成器执行到下一个 yield
,还会将 value
发送回生成器内部。这个发送的值会成为 上一个 yield
表达式的返回值。
第一次启动生成器时,必须使用 next()
或 send(None)
,因为此时没有“上一个 yield
”来接收发送的值。
“`python
def interactive_generator():
print(“生成器启动…”)
# 第一次 yield,没有值发送进来,y 的初始值将是 None
x = yield 10
print(f”生成器接收到值: {x}”) # x 会是外部 send() 发送的值
# 第二次 yield
y = yield x * 2
print(f"生成器接收到值: {y}") # y 会是外部第二次 send() 发送的值
print("生成器结束...")
# 没有 yield 后,函数执行完毕,下次调用会抛出 StopIteration
创建生成器对象
gen = interactive_generator()
启动生成器,执行到第一个 yield
print(“— 首次启动 —“)
first_yielded_value = next(gen) # 或 gen.send(None)
print(f”首次 yield 的值: {first_yielded_value}”)
print(“\n— 发送值并继续 —“)
发送值 20 到生成器,并继续执行到第二个 yield
second_yielded_value = gen.send(20) # 20 会被赋给内部变量 x
print(f”发送 20 后 yield 的值: {second_yielded_value}”)
print(“\n— 再次发送值并结束 —“)
发送值 30 到生成器,并继续执行直到结束
try:
gen.send(30) # 30 会被赋给内部变量 y
except StopIteration:
print(“生成器已结束。”)
“`
输出:
“`
— 首次启动 —
生成器启动…
首次 yield 的值: 10
— 发送值并继续 —
生成器接收到值: 20
发送 20 后 yield 的值: 40
— 再次发送值并结束 —
生成器接收到值: 30
生成器结束…
生成器已结束。
“`
send()
方法常用于实现协程(Coroutines),允许生成器暂停等待外部输入,并在接收到输入后继续执行。
7.2 throw(type, value=None, traceback=None)
:在生成器内部抛出异常
throw()
方法允许您在生成器的当前暂停位置抛出一个异常。如果生成器内部捕获了该异常,它可以处理并继续执行(可能 yield 新值或再次抛出异常)。如果异常未被捕获,它会从生成器中传播出来。
“`python
def error_handling_generator():
print(“生成器启动…”)
try:
yield 1
except ValueError as e:
print(f”生成器捕获到异常: {e}”)
yield 2 # 异常处理后可以继续 yield
yield 3
print("生成器结束...")
gen = error_handling_generator()
print(next(gen)) # 启动,yield 1
print(“— 在生成器内部抛出异常 —“)
try:
print(gen.throw(ValueError, “Something went wrong!”)) # 抛出 ValueError
except StopIteration:
print(“生成器已结束 (异常未处理或处理后结束)。”)
print(“— 继续执行 —“)
如果上一步异常被处理并 yield 了新值 2,这里会继续执行到 yield 3
try:
print(next(gen))
except StopIteration:
print(“生成器已结束 (已耗尽)。”)
“`
输出:
1
--- 在生成器内部抛出异常 ---
生成器捕获到异常: Something went wrong!
2
--- 继续执行 ---
3
生成器已结束 (已耗尽)。
throw()
在需要中断或修改生成器执行流程时非常有用,例如在外部检测到错误条件时通知生成器。
7.3 close()
:关闭生成器
close()
方法会强制停止生成器的执行。当 close()
被调用时,如果生成器在 try...finally
块中,finally
块会被执行,以进行必要的清理工作。之后,生成器会被设置为已耗尽状态,后续调用 next()
会直接抛出 StopIteration
。
“`python
def closing_generator():
print(“生成器启动…”)
try:
yield 1
yield 2
finally:
print(“生成器正在关闭…”)
print(“生成器已关闭。”) # 这行不会被执行,因为 finally 块执行后生成器就结束了
gen = closing_generator()
print(next(gen)) # yield 1
print(“— 关闭生成器 —“)
gen.close() # 调用 close()
print(“— 尝试继续 —“)
try:
print(next(gen))
except StopIteration:
print(“生成器已耗尽。”)
“`
输出:
1
--- 关闭生成器 ---
生成器正在关闭...
--- 尝试继续 ---
生成器已耗尽。
close()
主要用于资源清理,确保生成器在不再需要时能够释放持有的资源(如文件句柄)。
7.4 yield from iterable
:生成器委托
yield from
语句用于将生成器的一部分操作委托给另一个可迭代对象(可以是另一个生成器、列表、元组等)。它允许在一个生成器(称为委托生成器,Delegating Generator)中无缝地 yield
出另一个可迭代对象(称为子生成器,Subgenerator)产生的每一个值。
“`python
def sub_generator(x):
print(” 子生成器启动…”)
yield x + 1
yield x + 2
print(” 子生成器结束…”)
def main_generator():
print(“主生成器启动…”)
yield 10
# 委托给 sub_generator(20)
print(“主生成器委托给子生成器…”)
yield from sub_generator(20)
print(“主生成器从子生成器返回…”)
yield 30
print(“主生成器结束…”)
gen = main_generator()
print(next(gen)) # 主生成器 yield 10
print(next(gen)) # 通过 yield from,子生成器 yield 21
print(next(gen)) # 通过 yield from,子生成器 yield 22
print(next(gen)) # 子生成器结束,主生成器继续,yield 30
try:
next(gen) # 主生成器结束
except StopIteration:
print(“迭代完成。”)
“`
输出:
主生成器启动...
10
主生成器委托给子生成器...
子生成器启动...
子生成器结束...
21
22
主生成器从子生成器返回...
30
主生成器结束...
迭代完成。
yield from
的优势:
- 代码简化: 替代了手动遍历子可迭代对象并逐个
yield
的写法。 - 效率提升: 在委托给子生成器时,
yield from
提供了更高效的机制来传递值和异常。 - 双向通道:
yield from
不仅可以将子生成器产生的值传递给委托生成器的调用者,还可以将调用者通过send()
发送给委托生成器的值、通过throw()
抛出的异常传递给子生成器,并将子生成器通过return
返回的值传递给委托生成器(yield from
表达式的值就是子生成器的返回值)。
8. 实际应用示例
生成器在许多实际编程场景中都非常有用。
8.1 读取大文件
“`python
def read_large_file(filepath):
“””生成器函数:逐行读取大文件”””
print(f”开始逐行读取文件: {filepath}”)
try:
with open(filepath, ‘r’, encoding=’utf-8′) as f:
for line in f:
yield line.strip() # yield 每一行,并去除首尾空白
except FileNotFoundError:
print(f”错误:文件未找到 {filepath}”)
finally:
print(f”文件读取结束或发生错误,清理资源。”) # 如果文件打开成功,with 语句会自动关闭文件
假设有一个非常大的文件 ‘large_data.txt’
创建一个模拟的大文件
with open(‘large_data.txt’, ‘w’, encoding=’utf-8′) as f:
for i in range(1000000):
f.write(f”这是文件的第 {i+1} 行。\n”)
使用生成器逐行处理文件
print(“— 使用生成器处理文件 —“)
line_count = 0
for line in read_large_file(‘large_data.txt’):
# 在这里处理每一行数据,例如:打印、解析、写入新文件等
if line_count < 5 or line_count >= 999995: # 只打印开头和结尾几行
print(f”处理行 {line_count + 1}: {line}”)
elif line_count == 5:
print(“…”) # 模拟中间省略
line_count += 1
print(f”\n总共处理了 {line_count} 行。”)
如果使用 readlines(),可能会占用大量内存
try:
with open(‘large_data.txt’, ‘r’, encoding=’utf-8′) as f:
all_lines = f.readlines() # 一次性读取所有行到列表中
print(f”使用 readlines() 读取了 {len(all_lines)} 行。”)
except MemoryError:
print(“使用 readlines() 导致内存错误!”)
“`
使用生成器 read_large_file
,我们在任何时候都只在内存中保留一行数据(加上生成器的状态),而不是整个文件的内容。
8.2 生成无限序列
“`python
def natural_numbers():
“””生成器函数:生成无限自然数序列 (1, 2, 3, …)”””
n = 1
while True: # 无限循环
yield n
n += 1
创建自然数生成器
naturals = natural_numbers()
print(“— 生成前10个自然数 —“)
只取出前10个,不会导致无限循环问题
for i in range(10):
print(next(naturals))
print(“— 继续生成,再获取前5个偶数 —“)
可以从上次停止的地方继续
even_count = 0
for num in naturals:
if num % 2 == 0:
print(f”偶数: {num}”)
even_count += 1
if even_count >= 5:
break # 退出循环,生成器暂停
print(“— 生成器已暂停 —“)
如果需要,可以再次获取下一个自然数
print(f”下一个自然数是: {next(naturals)}”)
“`
while True
在普通函数中会导致死循环,但在生成器函数中,它配合 yield
实现了无限序列的按需生成。
8.3 构建数据处理管道
将多个生成器串联起来,形成一个数据处理管道,可以实现高效、模块化的数据转换。
“`python
def numbers_generator(n):
“””生成 1 到 n 的数字”””
print(“生成数字…”)
for i in range(1, n + 1):
yield i
print(“数字生成完毕.”)
def filter_even(numbers):
“””过滤出偶数”””
print(“开始过滤偶数…”)
for num in numbers:
print(f” 过滤中,检查数字: {num}”)
if num % 2 == 0:
yield num
print(“偶数过滤完毕.”)
def square_numbers(numbers):
“””计算数字的平方”””
print(“开始计算平方…”)
for num in numbers:
print(f” 计算平方中,对 {num}…”)
yield num * num
print(“平方计算完毕.”)
构建数据管道:生成数字 -> 过滤偶数 -> 计算平方
print(“— 构建并执行数据管道 —“)
gen_numbers = numbers_generator(10)
gen_even = filter_even(gen_numbers)
gen_squared = square_numbers(gen_even)
更简洁的链式调用方式
gen_squared = square_numbers(filter_even(numbers_generator(10)))
迭代处理结果
for result in gen_squared:
print(f”最终结果: {result}”)
print(“-” * 15)
“`
输出:
“`
— 构建并执行数据管道 —
开始计算平方…
开始过滤偶数…
生成数字…
过滤中,检查数字: 1
过滤中,检查数字: 2
计算平方中,对 2…
最终结果: 4
过滤中,检查数字: 3
过滤中,检查数字: 4
计算平方中,对 4…
最终结果: 16
过滤中,检查数字: 5
过滤中,检查数字: 6
计算平方中,对 6…
最终结果: 36
过滤中,检查数字: 7
过滤中,检查数字: 8
计算平方中,对 8…
最终结果: 64
过滤中,检查数字: 9
过滤中,检查数字: 10
计算平方中,对 10…
最终结果: 100
数字生成完毕.
偶数过滤完毕.
平方计算完毕.
“`
可以看到,数据流在生成器之间传递,每个生成器只在需要时处理数据,没有创建大型的中间列表,整个过程高效且内存友好。
9. 何时使用生成器?何时避免使用?
何时使用生成器:
- 需要处理或生成大量数据,而不能一次性加载到内存时。
- 需要处理无限序列时。
- 数据处理流程可以分解为一系列步骤,希望以管道方式处理数据时。
- 希望实现惰性计算,只在需要结果时才进行计算。
- 编写自定义迭代器时,使用生成器函数比编写一个完整的迭代器类更简单。
何时避免使用生成器:
- 数据集很小,内存不是问题时。使用列表或元组可能更直观,而且支持多次遍历和随机访问。
- 需要多次遍历同一个数据集时。生成器是一次性的,一旦耗尽,就需要重新创建。如果需要多次遍历,可以将生成器的结果转换为列表(如果内存允许)或者创建多个生成器实例。
- 需要随机访问序列中的元素时。生成器只能按顺序访问元素,不支持通过索引直接访问。
10. 总结
Python 生成器是处理序列数据流的强大工具,通过 yield
关键字实现了惰性计算和状态保存。它们使得编写高效、内存友好的代码成为可能,尤其在处理大数据或无限序列时优势显著。理解生成器、迭代器和可迭代对象之间的关系,掌握生成器函数和生成器表达式的用法,以及了解 send()
、throw()
、close()
和 yield from
等高级特性,将极大地提升您在 Python 中处理复杂数据流的能力。
无论是读取大型文件、构建数据处理管道,还是实现协程等高级模式,生成器都是 Python 开发者工具箱中不可或缺的一部分。熟练掌握生成器的使用,将让您的代码更加优雅、高效和强大。