Python 生成器与 yield:工作原理及示例 – wiki基地


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 在后台做了以下事情:

  1. 调用可迭代对象的 __iter__() 方法,获取一个迭代器对象。
  2. 重复调用迭代器对象的 __next__() 方法来获取下一个元素。
  3. __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 循环中使用它来驱动生成器函数的执行。

示例:returnyield 的对比

“`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]
列表推导式结果类型:
列表推导式结果占用的内存大致(元素数量 * 每个元素大小)…

生成器表达式结果: at …>
生成器表达式结果类型:
生成器表达式本身占用的内存很小…

迭代生成器表达式:
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. 生成器的主要优势

总结来说,使用生成器主要有以下几个显著优势:

  1. 内存效率 (Memory Efficiency): 这是生成器最大的优势。它们采用惰性求值,只在需要时生成下一个元素,而不是一次性将所有元素加载到内存中。这使得生成器非常适合处理大型数据集或无限序列。
  2. 处理无限序列 (Handling Infinite Sequences): 使用 while True 循环结合 yield 可以轻松创建表示无限序列的生成器。由于是按需生成,即使序列无限,程序也不会耗尽内存。
  3. 提高性能和响应速度 (Improved Performance and Responsiveness): 对于大型数据集,生成器可以立即开始处理和产生第一个结果,而不需要等待整个数据集加载和处理完毕。这可以提高程序的启动速度和用户响应性。
  4. 管道化数据处理 (Pipelining Data Processing): 生成器可以很方便地链式调用,形成数据处理管道。一个生成器的输出可以作为另一个生成器的输入,从而实现复杂的数据转换和过滤流程,而无需创建中间的完整列表。
  5. 代码简洁性 (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 开发者工具箱中不可或缺的一部分。熟练掌握生成器的使用,将让您的代码更加优雅、高效和强大。


发表评论

您的邮箱地址不会被公开。 必填项已用 * 标注

滚动至顶部