Python 中的 yield 详解
1. 什么是 yield
1.1 yield 的基本概念
yield 是 Python 中的一个关键字,用于定义生成器函数(Generator Function)。当一个函数中包含 yield 关键字时,这个函数就变成了一个生成器函数。
简单理解:
yield类似于return,但不会终止函数的执行- 使用
yield的函数可以”暂停”执行,并在需要时”恢复”执行 - 每次
yield会返回一个值,但函数的状态会被保存,下次调用时从上次暂停的地方继续
1.2 为什么需要 yield
在传统的函数中,使用 return 返回一个值后,函数就结束了。但有时候我们需要:
- 按需生成数据:不需要一次性生成所有数据,而是需要时才生成
- 节省内存:处理大量数据时,不需要将所有数据都加载到内存中
- 实现协程:在异步编程中,可以暂停和恢复函数的执行
1.3 最简单的 yield 示例
让我们从一个最简单的例子开始:
# 示例1:最简单的生成器函数
def simple_generator():
print("开始执行")
yield 1
print("继续执行")
yield 2
print("继续执行")
yield 3
print("执行结束")
# 调用生成器函数,返回一个生成器对象
gen = simple_generator()
print(type(gen)) # <class 'generator'>
# 使用 next() 函数获取下一个值
print(next(gen)) # 输出:开始执行,然后输出:1
print(next(gen)) # 输出:继续执行,然后输出:2
print(next(gen)) # 输出:继续执行,然后输出:3
# print(next(gen)) # 如果继续调用,会抛出 StopIteration 异常
运行结果:
<class 'generator'>
开始执行
1
继续执行
2
继续执行
3
关键点:
- 调用生成器函数不会立即执行函数体,而是返回一个生成器对象
- 每次调用
next()时,函数会执行到下一个yield语句 yield后面的值会被返回,函数会暂停在这里- 再次调用
next()时,函数从上次暂停的地方继续执行
2. yield 与 return 的区别
2.1 基本区别对比
| 特性 | return | yield |
|---|---|---|
| 函数类型 | 普通函数 | 生成器函数 |
| 返回值 | 立即返回一个值 | 返回一个生成器对象 |
| 执行状态 | 函数执行完毕,状态丢失 | 函数暂停,状态保存 |
| 多次调用 | 每次调用都从头开始 | 从上次暂停的地方继续 |
| 内存占用 | 需要存储所有返回值 | 只存储当前状态 |
2.2 对比示例:return vs yield
# 示例2:使用 return 的普通函数
def normal_function():
return 1
return 2 # 这行代码永远不会执行
return 3 # 这行代码永远不会执行
result = normal_function()
print(result) # 输出:1
# 示例3:使用 yield 的生成器函数
def generator_function():
yield 1
yield 2
yield 3
gen = generator_function()
print(next(gen)) # 输出:1
print(next(gen)) # 输出:2
print(next(gen)) # 输出:3
关键区别:
return执行后函数立即结束,后面的代码不会执行yield执行后函数暂停,下次调用时继续执行后面的代码
2.3 生成多个值的对比
# 示例4:普通函数返回列表(需要一次性生成所有值)
def get_numbers_list(n):
result = []
for i in range(n):
result.append(i * i)
return result # 返回完整的列表
numbers = get_numbers_list(5)
print(numbers) # 输出:[0, 1, 4, 9, 16]
print(f"内存占用:需要存储 {len(numbers)} 个元素")
# 示例5:生成器函数(按需生成值)
def get_numbers_generator(n):
for i in range(n):
yield i * i # 每次只生成一个值
gen = get_numbers_generator(5)
print(list(gen)) # 输出:[0, 1, 4, 9, 16]
# 注意:生成器只能遍历一次
# print(list(gen)) # 输出:[]
# 或者逐个获取
gen = get_numbers_generator(5)
for num in gen:
print(num, end=" ") # 输出:0 1 4 9 16
3. 生成器函数和生成器对象
3.1 生成器函数(Generator Function)
生成器函数是包含 yield 关键字的函数。定义生成器函数和定义普通函数的方法相同,只是使用 yield 代替 return。
# 示例6:生成器函数的定义
def my_generator():
yield "第一个值"
yield "第二个值"
yield "第三个值"
3.2 生成器对象(Generator Object)
调用生成器函数时,不会执行函数体,而是返回一个生成器对象。
# 示例7:生成器对象
def count_up_to(n):
count = 1
while count <= n:
yield count
count += 1
# 调用生成器函数,返回生成器对象
gen = count_up_to(5)
print(gen) # 输出:<generator object count_up_to at 0x...>
print(type(gen)) # 输出:<class 'generator'>
3.3 生成器对象的特性
生成器对象是一个迭代器(Iterator),具有以下特性:
- 可以使用
next()函数获取下一个值 - 可以使用
for循环遍历 - 只能遍历一次
- 是惰性求值的(Lazy Evaluation)
# 示例8:生成器对象的多种使用方式
def number_generator():
yield 10
yield 20
yield 30
gen = number_generator()
# 方式1:使用 next() 函数
print(next(gen)) # 输出:10
print(next(gen)) # 输出:20
print(next(gen)) # 输出:30
# 方式2:使用 for 循环
gen = number_generator()
for num in gen:
print(num) # 输出:10, 20, 30
# 方式3:转换为列表
gen = number_generator()
print(list(gen)) # 输出:[10, 20, 30]
# 方式4:使用 * 解包
gen = number_generator()
print(*gen) # 输出:10 20 30
4. yield 的基本用法
4.1 生成数字序列
# 示例9:生成从 1 到 n 的数字
def number_sequence(n):
for i in range(1, n + 1):
yield i
# 使用生成器
for num in number_sequence(5):
print(num, end=" ") # 输出:1 2 3 4 5
4.2 生成斐波那契数列
# 示例10:生成斐波那契数列
def fibonacci(n):
"""生成前 n 个斐波那契数"""
a, b = 0, 1
count = 0
while count < n:
yield a
a, b = b, a + b
count += 1
# 生成前 10 个斐波那契数
for num in fibonacci(10):
print(num, end=" ") # 输出:0 1 1 2 3 5 8 13 21 34
4.3 生成无限序列
# 示例11:生成无限的自然数序列
def natural_numbers():
"""生成无限的自然数序列"""
num = 1
while True:
yield num
num += 1
# 使用生成器(注意:不要直接转换为列表,会无限循环)
gen = natural_numbers()
print(next(gen)) # 输出:1
print(next(gen)) # 输出:2
print(next(gen)) # 输出:3
# 获取前 10 个自然数
gen = natural_numbers()
for i, num in enumerate(gen):
if i >= 10:
break
print(num, end=" ") # 输出:1 2 3 4 5 6 7 8 9 10
4.4 处理文件内容
# 示例12:逐行读取文件(内存友好)
def read_file_lines(filename):
"""逐行读取文件,每次只加载一行到内存"""
with open(filename, 'r', encoding='utf-8') as f:
for line in f:
yield line.strip() # 去除行尾的换行符
# 使用生成器处理大文件
for line in read_file_lines('large_file.txt'):
print(line)
# 处理每一行,不需要一次性加载整个文件到内存
4.5 过滤数据
# 示例13:过滤偶数
def filter_even(numbers):
"""过滤出偶数"""
for num in numbers:
if num % 2 == 0:
yield num
# 使用生成器过滤
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
even_numbers = filter_even(numbers)
print(list(even_numbers)) # 输出:[2, 4, 6, 8, 10]
4.6 转换数据
# 示例14:将数字转换为平方
def square_numbers(numbers):
"""将数字列表转换为平方"""
for num in numbers:
yield num ** 2
# 使用生成器转换
numbers = [1, 2, 3, 4, 5]
squares = square_numbers(numbers)
print(list(squares)) # 输出:[1, 4, 9, 16, 25]
5. 生成器的执行流程
5.1 执行流程详解
理解生成器的执行流程对于掌握 yield 非常重要。让我们通过一个详细的例子来说明:
# 示例15:详细演示生成器的执行流程
def detailed_generator():
print("步骤1:函数开始执行")
yield "第一个值"
print("步骤2:从第一个 yield 后继续执行")
yield "第二个值"
print("步骤3:从第二个 yield 后继续执行")
yield "第三个值"
print("步骤4:函数执行完毕")
# 创建生成器对象(此时函数体还没有执行)
print("创建生成器对象...")
gen = detailed_generator()
print("生成器对象已创建,但函数体尚未执行n")
# 第一次调用 next()
print("第一次调用 next():")
result1 = next(gen)
print(f"返回值:{result1}n")
# 第二次调用 next()
print("第二次调用 next():")
result2 = next(gen)
print(f"返回值:{result2}n")
# 第三次调用 next()
print("第三次调用 next():")
result3 = next(gen)
print(f"返回值:{result3}n")
# 第四次调用 next() 会抛出 StopIteration 异常
print("第四次调用 next()(会抛出异常):")
try:
result4 = next(gen)
except StopIteration as e:
print(f"捕获到 StopIteration 异常:{e}")
运行结果:
创建生成器对象...
生成器对象已创建,但函数体尚未执行
第一次调用 next():
步骤1:函数开始执行
返回值:第一个值
第二次调用 next():
步骤2:从第一个 yield 后继续执行
返回值:第二个值
第三次调用 next():
步骤3:从第二个 yield 后继续执行
返回值:第三个值
第四次调用 next()(会抛出异常):
步骤4:函数执行完毕
捕获到 StopIteration 异常:
5.2 执行流程图解
调用生成器函数
↓
返回生成器对象(函数体未执行)
↓
第一次调用 next()
↓
执行到第一个 yield → 返回第一个值 → 暂停
↓
第二次调用 next()
↓
从第一个 yield 后继续执行 → 执行到第二个 yield → 返回第二个值 → 暂停
↓
第三次调用 next()
↓
从第二个 yield 后继续执行 → 执行到第三个 yield → 返回第三个值 → 暂停
↓
第四次调用 next()
↓
从第三个 yield 后继续执行 → 函数执行完毕 → 抛出 StopIteration 异常
5.3 带循环的生成器执行流程
# 示例16:带循环的生成器执行流程
def loop_generator(n):
print(f"生成器开始,n = {n}")
for i in range(n):
print(f"循环中,i = {i}")
yield i * 2
print(f"yield 后继续,i = {i}")
print("生成器结束")
gen = loop_generator(3)
print("第一次调用 next():")
print(f"返回值:{next(gen)}n")
print("第二次调用 next():")
print(f"返回值:{next(gen)}n")
print("第三次调用 next():")
print(f"返回值:{next(gen)}n")
运行结果:
第一次调用 next():
生成器开始,n = 3
循环中,i = 0
返回值:0
第二次调用 next():
yield 后继续,i = 0
循环中,i = 1
返回值:2
第三次调用 next():
yield 后继续,i = 1
循环中,i = 2
返回值:4
6. 生成器的常用方法
6.1 next() 方法
next() 函数用于获取生成器的下一个值。
# 示例17:使用 next() 方法
def simple_gen():
yield 1
yield 2
yield 3
gen = simple_gen()
print(next(gen)) # 输出:1
print(next(gen)) # 输出:2
print(next(gen)) # 输出:3
# print(next(gen)) # 抛出 StopIteration 异常
6.2 send() 方法
send() 方法可以向生成器发送值,这个值会成为 yield 表达式的返回值。
# 示例18:使用 send() 方法向生成器发送值
def generator_with_send():
print("开始执行")
value1 = yield "第一个值"
print(f"收到 send 的值:{value1}")
value2 = yield "第二个值"
print(f"收到 send 的值:{value2}")
yield "第三个值"
gen = generator_with_send()
# 第一次必须使用 next() 或 send(None) 启动生成器
print(next(gen)) # 输出:开始执行,然后输出:第一个值
# 使用 send() 发送值
print(gen.send("Hello")) # 输出:收到 send 的值:Hello,然后输出:第二个值
# 继续发送值
print(gen.send("World")) # 输出:收到 send 的值:World,然后输出:第三个值
重要提示:
- 第一次调用生成器时,必须使用
next()或send(None)来启动生成器 send()的值会成为yield表达式的返回值- 如果生成器还没有执行到第一个
yield,不能使用send()发送非 None 的值
6.3 throw() 方法
throw() 方法用于在生成器中抛出异常。
# 示例19:使用 throw() 方法抛出异常
def generator_with_throw():
try:
yield 1
yield 2
yield 3
except ValueError as e:
print(f"捕获到异常:{e}")
yield "异常处理后的值"
gen = generator_with_throw()
print(next(gen)) # 输出:1
# 在生成器中抛出异常
print(gen.throw(ValueError, "这是一个错误")) # 输出:捕获到异常:这是一个错误,然后输出:异常处理后的值
6.4 close() 方法
close() 方法用于关闭生成器,会在生成器中抛出 GeneratorExit 异常。
# 示例20:使用 close() 方法关闭生成器
def generator_with_close():
try:
yield 1
yield 2
yield 3
except GeneratorExit:
print("生成器被关闭")
gen = generator_with_close()
print(next(gen)) # 输出:1
# 关闭生成器
gen.close()
print("生成器已关闭")
6.5 综合示例:所有方法的使用
# 示例21:综合使用生成器的各种方法
def advanced_generator():
print("生成器启动")
try:
value = yield "初始值"
print(f"收到值:{value}")
while True:
try:
value = yield f"处理值:{value}"
print(f"收到值:{value}")
except ValueError as e:
print(f"捕获 ValueError:{e}")
value = yield f"错误处理:{e}"
except GeneratorExit:
print("生成器正常关闭")
finally:
print("清理资源")
gen = advanced_generator()
# 启动生成器
print(next(gen)) # 输出:生成器启动,然后输出:初始值
# 发送值
print(gen.send("Hello")) # 输出:收到值:Hello,然后输出:处理值:Hello
# 抛出异常
print(gen.throw(ValueError, "测试错误")) # 输出:捕获 ValueError:测试错误,然后输出:错误处理:测试错误
# 关闭生成器
gen.close() # 输出:生成器正常关闭,然后输出:清理资源
7. yield 的进阶用法
7.1 yield from(委托生成器)
yield from 是 Python 3.3 引入的语法,用于委托给另一个生成器。
# 示例22:使用 yield from 委托生成器
def inner_generator():
yield 1
yield 2
yield 3
def outer_generator():
yield "开始"
yield from inner_generator() # 委托给 inner_generator
yield "结束"
# 使用 yield from
for value in outer_generator():
print(value, end=" ") # 输出:开始 1 2 3 结束
7.2 yield from 的优势
yield from 可以简化代码,避免手动迭代:
# 示例23:不使用 yield from(繁琐的方式)
def generator_without_yield_from():
yield 1
for value in range(2, 5):
yield value
yield 5
# 示例24:使用 yield from(简洁的方式)
def generator_with_yield_from():
yield 1
yield from range(2, 5)
yield 5
# 两种方式结果相同
print(list(generator_without_yield_from())) # 输出:[1, 2, 3, 4, 5]
print(list(generator_with_yield_from())) # 输出:[1, 2, 3, 4, 5]
7.3 yield from 处理嵌套生成器
# 示例25:处理嵌套的生成器
def level1():
yield "Level 1 - 1"
yield "Level 1 - 2"
def level2():
yield "Level 2 - 1"
yield from level1()
yield "Level 2 - 2"
def level3():
yield "Level 3 - 1"
yield from level2()
yield "Level 3 - 2"
# 使用嵌套生成器
for value in level3():
print(value)
输出:
Level 3 - 1
Level 2 - 1
Level 1 - 1
Level 1 - 2
Level 2 - 2
Level 3 - 2
7.4 带返回值的 yield from
yield from 还可以接收子生成器的返回值:
# 示例26:yield from 接收返回值
def sub_generator():
yield 1
yield 2
return "子生成器完成" # 返回值会被 yield from 接收
def main_generator():
result = yield from sub_generator()
yield f"主生成器收到:{result}"
gen = main_generator()
for value in gen:
print(value)
输出:
1
2
主生成器收到:子生成器完成
7.5 协程(Coroutine)基础
生成器可以用作协程,实现简单的协作式多任务:
# 示例27:简单的协程示例
def producer():
"""生产者协程"""
for i in range(5):
value = yield f"产品 {i}"
print(f"生产者收到反馈:{value}")
def consumer(prod):
"""消费者协程"""
next(prod) # 启动生产者
for i in range(5):
product = prod.send(f"消费反馈 {i}")
print(f"消费者收到:{product}")
# 使用协程
prod = producer()
consumer(prod)
7.6 双向通信的生成器
# 示例28:双向通信的生成器
def echo_generator():
"""回显生成器,将收到的值原样返回"""
while True:
value = yield
yield value # 返回收到的值
gen = echo_generator()
next(gen) # 启动生成器
print(gen.send("Hello")) # 输出:Hello
next(gen) # 跳过 yield None
print(gen.send("World")) # 输出:World
8. 生成器表达式
8.1 什么是生成器表达式
生成器表达式(Generator Expression)是创建生成器的简洁语法,类似于列表推导式,但使用圆括号而不是方括号。
# 示例29:生成器表达式的基本语法
# 列表推导式(立即生成所有值)
squares_list = [x**2 for x in range(5)]
print(squares_list) # 输出:[0, 1, 4, 9, 16]
# 生成器表达式(按需生成值)
squares_gen = (x**2 for x in range(5))
print(squares_gen) # 输出:<generator object <genexpr> at 0x...>
print(list(squares_gen)) # 输出:[0, 1, 4, 9, 16]
8.2 生成器表达式 vs 列表推导式
# 示例30:内存使用对比
import sys
# 列表推导式:占用更多内存
numbers_list = [x**2 for x in range(1000000)]
print(f"列表大小:{sys.getsizeof(numbers_list)} 字节")
# 生成器表达式:占用很少内存
numbers_gen = (x**2 for x in range(1000000))
print(f"生成器大小:{sys.getsizeof(numbers_gen)} 字节")
8.3 生成器表达式的应用
# 示例31:使用生成器表达式处理数据
# 计算前 10 个偶数的平方和
sum_of_squares = sum(x**2 for x in range(2, 21, 2))
print(sum_of_squares) # 输出:1540
# 过滤数据
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
even_squares = (x**2 for x in numbers if x % 2 == 0)
print(list(even_squares)) # 输出:[4, 16, 36, 64, 100]
8.4 嵌套生成器表达式
# 示例32:嵌套生成器表达式
# 生成所有可能的坐标对
coordinates = ((x, y) for x in range(3) for y in range(3))
print(list(coordinates))
# 输出:[(0, 0), (0, 1), (0, 2), (1, 0), (1, 1), (1, 2), (2, 0), (2, 1), (2, 2)]
9. 实际应用场景
9.1 读取大文件
处理大文件时,使用生成器可以避免内存溢出:
# 示例33:逐行读取大文件
def read_large_file(filename):
"""逐行读取大文件,避免内存溢出"""
with open(filename, 'r', encoding='utf-8') as f:
for line in f:
yield line.strip()
# 处理大文件
for line in read_large_file('large_file.txt'):
# 处理每一行
if 'error' in line.lower():
print(f"发现错误行:{line}")
9.2 数据管道处理
生成器可以构建数据管道,实现流式处理:
# 示例34:数据管道处理
def read_numbers():
"""读取数字"""
for i in range(1, 11):
yield i
def filter_even(numbers):
"""过滤偶数"""
for num in numbers:
if num % 2 == 0:
yield num
def square(numbers):
"""计算平方"""
for num in numbers:
yield num ** 2
# 构建数据管道
pipeline = square(filter_even(read_numbers()))
print(list(pipeline)) # 输出:[4, 16, 36, 64, 100]
9.3 无限序列生成
生成器非常适合生成无限序列:
# 示例35:生成无限序列
def count_from(start):
"""从指定数字开始无限计数"""
while True:
yield start
start += 1
def fibonacci_infinite():
"""无限斐波那契数列"""
a, b = 0, 1
while True:
yield a
a, b = b, a + b
# 获取前 10 个斐波那契数
fib = fibonacci_infinite()
for i, num in enumerate(fib):
if i >= 10:
break
print(num, end=" ") # 输出:0 1 1 2 3 5 8 13 21 34
9.4 分页处理数据
# 示例36:分页处理数据
def paginate(data, page_size=10):
"""分页生成器"""
for i in range(0, len(data), page_size):
yield data[i:i + page_size]
# 使用分页生成器
data = list(range(100))
for page in paginate(data, page_size=20):
print(f"处理页面:{page[:5]}...") # 只显示前5个元素
9.5 实现迭代器协议
生成器自动实现了迭代器协议:
# 示例37:自定义可迭代对象
class NumberRange:
"""自定义数字范围类"""
def __init__(self, start, end):
self.start = start
self.end = end
def __iter__(self):
"""返回生成器作为迭代器"""
current = self.start
while current < self.end:
yield current
current += 1
# 使用自定义可迭代对象
num_range = NumberRange(1, 6)
for num in num_range:
print(num, end=" ") # 输出:1 2 3 4 5
9.6 状态机实现
生成器可以用于实现简单的状态机:
# 示例38:使用生成器实现状态机
def state_machine():
"""简单的状态机"""
state = "开始"
while True:
if state == "开始":
command = yield "状态:开始"
if command == "下一步":
state = "处理中"
elif state == "处理中":
command = yield "状态:处理中"
if command == "完成":
state = "结束"
elif command == "返回":
state = "开始"
elif state == "结束":
yield "状态:结束"
break
# 使用状态机
sm = state_machine()
print(next(sm)) # 输出:状态:开始
print(sm.send("下一步")) # 输出:状态:处理中
print(sm.send("完成")) # 输出:状态:结束
9.7 数据流处理
# 示例39:数据流处理示例
def data_source():
"""模拟数据源"""
for i in range(1, 11):
yield {"id": i, "value": i * 10}
def filter_data(data_stream, min_value=30):
"""过滤数据"""
for item in data_stream:
if item["value"] >= min_value:
yield item
def transform_data(data_stream):
"""转换数据"""
for item in data_stream:
item["value"] = item["value"] * 2
yield item
# 构建数据处理管道
pipeline = transform_data(filter_data(data_source(), min_value=30))
for item in pipeline:
print(item)
输出:
{'id': 3, 'value': 60}
{'id': 4, 'value': 80}
{'id': 5, 'value': 100}
{'id': 6, 'value': 120}
{'id': 7, 'value': 140}
{'id': 8, 'value': 160}
{'id': 9, 'value': 180}
{'id': 10, 'value': 200}
10. 常见问题与注意事项
10.1 生成器只能遍历一次
生成器是一次性的,遍历完后就不能再次使用:
# 示例40:生成器只能遍历一次
def simple_gen():
yield 1
yield 2
yield 3
gen = simple_gen()
print(list(gen)) # 输出:[1, 2, 3]
print(list(gen)) # 输出:[](已经遍历完了)
解决方案:如果需要多次遍历,可以重新创建生成器:
# 示例41:重新创建生成器
def get_numbers():
yield 1
yield 2
yield 3
# 第一次遍历
gen1 = get_numbers()
print(list(gen1)) # 输出:[1, 2, 3]
# 第二次遍历(重新创建)
gen2 = get_numbers()
print(list(gen2)) # 输出:[1, 2, 3]
10.2 yield 不能在普通函数中使用
yield 只能在生成器函数中使用,不能在普通函数中使用:
# 示例42:错误用法
def normal_function():
return 1
yield 2 # 这行代码永远不会执行,但不会报错
# 实际上,包含 yield 的函数就是生成器函数
gen = normal_function()
print(type(gen)) # 输出:<class 'generator'>
10.3 生成器函数中的 return
在生成器函数中,return 用于结束生成器,但不能返回值(Python 3.3+ 可以返回值,但会被 yield from 接收):
# 示例43:生成器函数中的 return
def generator_with_return():
yield 1
yield 2
return "结束" # 这个返回值不能直接获取
# yield 3 # 这行代码永远不会执行
gen = generator_with_return()
for value in gen:
print(value) # 输出:1, 2
10.4 异常处理
生成器中的异常处理需要特别注意:
# 示例44:生成器中的异常处理
def generator_with_exception():
try:
yield 1
yield 2
raise ValueError("测试异常")
yield 3
except ValueError as e:
print(f"捕获异常:{e}")
yield "异常处理"
gen = generator_with_exception()
for value in gen:
print(value)
输出:
1
2
捕获异常:测试异常
异常处理
10.5 内存泄漏问题
虽然生成器可以节省内存,但如果不正确使用,也可能导致问题:
# 示例45:避免在生成器中保存大量引用
def problematic_generator():
large_data = [x for x in range(1000000)] # 这会占用大量内存
for item in large_data:
yield item
# 更好的方式
def better_generator():
for x in range(1000000): # 不保存所有数据
yield x
10.6 生成器的性能考虑
# 示例46:性能对比
import time
# 方式1:使用列表
def process_with_list(n):
numbers = [x**2 for x in range(n)]
return sum(numbers)
# 方式2:使用生成器
def process_with_generator(n):
numbers = (x**2 for x in range(n))
return sum(numbers)
# 对于小数据量,性能差异不大
# 对于大数据量,生成器可以节省内存
11. 性能优势
11.1 内存使用对比
生成器的主要优势是节省内存:
# 示例47:内存使用对比
import sys
# 列表:占用大量内存
numbers_list = [x for x in range(1000000)]
print(f"列表内存占用:{sys.getsizeof(numbers_list)} 字节")
# 生成器:占用很少内存
numbers_gen = (x for x in range(1000000))
print(f"生成器内存占用:{sys.getsizeof(numbers_gen)} 字节")
11.2 延迟计算(惰性求值)
生成器采用延迟计算,只在需要时才计算值:
# 示例48:延迟计算示例
def expensive_operation(n):
"""模拟耗时操作"""
print(f"计算 {n} 的平方...")
return n ** 2
def lazy_generator():
"""延迟计算的生成器"""
for i in range(5):
yield expensive_operation(i)
# 只有在实际获取值时才执行计算
gen = lazy_generator()
print("生成器已创建,但还没有执行计算")
# 现在才开始计算
print(next(gen)) # 输出:计算 0 的平方...,然后输出:0
print(next(gen)) # 输出:计算 1 的平方...,然后输出:1
11.3 处理无限序列
生成器可以处理无限序列,而列表不能:
# 示例49:处理无限序列
def infinite_sequence():
"""无限序列生成器"""
num = 0
while True:
yield num
num += 1
# 可以安全地创建无限序列生成器
gen = infinite_sequence()
# 获取前 10 个值
for i, value in enumerate(gen):
if i >= 10:
break
print(value, end=" ") # 输出:0 1 2 3 4 5 6 7 8 9
11.4 流式处理大数据
生成器非常适合流式处理大数据:
# 示例50:流式处理大数据
def process_large_dataset():
"""处理大型数据集"""
# 模拟从数据库或文件读取大量数据
for i in range(1000000):
# 每次只处理一条记录,不占用大量内存
yield {"id": i, "data": f"记录 {i}"}
# 流式处理,内存占用很小
for record in process_large_dataset():
# 处理每条记录
if record["id"] % 100000 == 0:
print(f"处理进度:{record['id']}")
12. 总结
12.1 核心要点回顾
-
yield 关键字:
yield用于定义生成器函数- 函数中包含
yield就自动成为生成器函数 yield会暂停函数执行,保存状态,下次从暂停处继续
-
生成器对象:
- 调用生成器函数返回生成器对象
- 生成器对象是迭代器,可以使用
next()和for循环 - 生成器只能遍历一次
-
主要优势:
- 节省内存:按需生成值,不需要一次性生成所有数据
- 延迟计算:只在需要时才计算值
- 处理无限序列:可以生成和处理无限序列
- 代码简洁:使用生成器表达式可以写出更简洁的代码
-
常用方法:
next():获取下一个值send():向生成器发送值throw():在生成器中抛出异常close():关闭生成器
-
进阶用法:
yield from:委托给另一个生成器- 生成器表达式:创建生成器的简洁语法
- 协程:使用生成器实现协作式多任务
12.2 使用建议
-
何时使用生成器:
- 处理大量数据时
- 需要按需生成数据时
- 实现数据管道时
- 处理无限序列时
-
何时不使用生成器:
- 需要多次遍历数据时(除非重新创建生成器)
- 需要随机访问数据时(生成器不支持索引)
- 数据量很小,性能差异可以忽略时
-
最佳实践:
- 使用
yield from简化嵌套生成器 - 使用生成器表达式处理简单场景
- 注意生成器只能遍历一次的特性
- 合理使用异常处理
- 使用
12.3 学习路径建议
对于新手,建议按以下顺序学习:
- 第一步:理解
yield的基本概念和与return的区别 - 第二步:掌握生成器函数和生成器对象的基本用法
- 第三步:学习生成器的执行流程和常用方法
- 第四步:练习使用生成器解决实际问题
- 第五步:学习
yield from和生成器表达式 - 第六步:探索生成器在协程和异步编程中的应用
12.4 进一步学习
掌握了 yield 的基础后,可以进一步学习:
- 异步编程:
async/await语法(Python 3.5+) - 协程:使用生成器实现更复杂的协程
- 迭代器协议:深入理解 Python 的迭代器机制
- 函数式编程:结合
map、filter、reduce等函数使用生成器
附录:完整示例代码
附录A:综合示例 – 数据管道
# 完整的数据处理管道示例
def read_data():
"""读取数据"""
for i in range(1, 21):
yield {"id": i, "value": i * 10}
def filter_data(data_stream, threshold=50):
"""过滤数据"""
for item in data_stream:
if item["value"] >= threshold:
yield item
def transform_data(data_stream):
"""转换数据"""
for item in data_stream:
item["value"] = item["value"] * 2
item["processed"] = True
yield item
def aggregate_data(data_stream):
"""聚合数据"""
total = 0
count = 0
for item in data_stream:
total += item["value"]
count += 1
yield item
yield {"summary": {"total": total, "count": count}}
# 构建完整的数据处理管道
pipeline = aggregate_data(
transform_data(
filter_data(
read_data(),
threshold=50
)
)
)
# 处理数据
for item in pipeline:
print(item)
附录B:综合示例 – 文件处理
# 完整的文件处理示例
def read_file_lines(filename):
"""逐行读取文件"""
try:
with open(filename, 'r', encoding='utf-8') as f:
for line_num, line in enumerate(f, 1):
yield {
"line_num": line_num,
"content": line.strip(),
"length": len(line.strip())
}
except FileNotFoundError:
yield {"error": f"文件 {filename} 未找到"}
def filter_lines(line_stream, min_length=10):
"""过滤行"""
for line_info in line_stream:
if "error" in line_info:
yield line_info
elif line_info.get("length", 0) >= min_length:
yield line_info
def process_lines(line_stream):
"""处理行"""
for line_info in line_stream:
if "error" not in line_info:
line_info["processed"] = True
line_info["upper"] = line_info["content"].upper()
yield line_info
# 使用文件处理管道
pipeline = process_lines(
filter_lines(
read_file_lines("example.txt"),
min_length=10
)
)
for result in pipeline:
print(result)
恭喜! 你已经完成了 Python 中 yield 的详细学习。记住,实践是掌握编程的关键,多写代码,多尝试不同的场景,你会越来越熟练地使用 yield 和生成器!