Python 生成器(Generator)
目录
- 什么是生成器
- 生成器语法
- 生成器函数
- 生成器表达式
- 生成器的工作原理
- 使用方法
- 通过next()函数
- 通过for循环
- 其他方法
- 应用场景
- 生成器表达式与列表推导式的区别
- 常见问题与陷阱
什么是生成器
生成器(Generator)是Python中的一种特殊类型的迭代器(Iterator),它能够按需生成值,而不是一次性生成所有值。与列表、元组等容器类型的可迭代对象不同,生成器不会将所有数据存储在内存中,而是在每次请求时"即时"生成数据。
生成器的关键特点:
- 是特殊的迭代器,遵循迭代器协议
- 使用
yield
语句生成值并暂停执行 - 函数状态会被保存,下次调用时从暂停处继续
- 每次调用只提供一个数据
- 不能"回头"访问已生成的值
- 支持
next()
函数调用 - 不支持下标索引访问
- 惰性计算,按需生成数据
生成器语法
Python中创建生成器有两种方式:通过生成器函数或生成器表达式。
生成器函数
生成器函数是包含yield
关键字的常规函数。当调用生成器函数时,它返回一个生成器对象,而不执行函数体。
def simple_generator():"""一个简单的生成器函数"""print("开始执行")yield 1print("第一次yield之后")yield 2print("第二次yield之后")yield 3print("结束")# 创建生成器对象
gen = simple_generator()
# 此时函数体尚未执行
生成器表达式
生成器表达式是创建生成器的简洁语法,类似于列表推导式,但使用圆括号而不是方括号。
# 生成器表达式
gen_expr = (x ** 2 for x in range(5))
生成器的工作原理
生成器的核心机制是函数执行的暂停与恢复:
- 当调用生成器函数时,返回一个生成器对象,但函数体不执行
- 当第一次调用
next()
方法时,函数开始执行,直到遇到yield
语句 yield
语句返回一个值,并且函数执行暂停,保存所有当前的状态(局部变量等)- 下一次调用
next()
方法时,函数从上次暂停的地方继续执行 - 当函数执行完毕时,生成器抛出
StopIteration
异常
这种机制使得生成器能够"记住"它的状态,并在每次需要时生成下一个值。
使用方法
通过next()函数
可以使用内置的next()
函数来获取生成器的下一个值:
def count_up_to(max):count = 1while count <= max:yield countcount += 1counter = count_up_to(3)
print(next(counter)) # 输出: 1
print(next(counter)) # 输出: 2
print(next(counter)) # 输出: 3try:print(next(counter)) # 抛出StopIteration异常
except StopIteration:print("生成器已耗尽")
通过for循环
更常见的是使用for
循环遍历生成器的值:
# 使用for循环遍历生成器
for num in count_up_to(5):print(num)
# 输出: 1 2 3 4 5
其他方法
生成器对象还有其他高级方法:
- send() - 向生成器内部发送值
def echo_generator():value = yield "准备接收"print(f"收到: {value}")while True:value = yield f"你发送了: {value}"print(f"收到: {value}")gen = echo_generator()
print(next(gen)) # 初始化生成器,输出: 准备接收
print(gen.send("你好")) # 发送值并获取下一个值,输出: 你发送了: 你好
- throw() - 向生成器内部抛出异常
def generator_with_exception():try:yield 1yield 2except ValueError:yield 'Got ValueError!'g = generator_with_exception()
print(next(g)) # 输出: 1
print(g.throw(ValueError)) # 输出: Got ValueError!
- close() - 关闭生成器
def closable_generator():try:yield 1yield 2yield 3finally:print("生成器被关闭")g = closable_generator()
print(next(g)) # 输出: 1
g.close() # 关闭生成器,输出: 生成器被关闭
应用场景
1. 处理大文件
生成器特别适合处理大文件,因为它们不需要一次性将整个文件加载到内存中:
def read_large_file(file_path):with open(file_path, 'r') as file:for line in file:yield line.strip()# 逐行处理大文件
for line in read_large_file('huge_file.txt'):# 处理每一行...pass
2. 生成无限序列
生成器可以轻松创建无限序列,只在需要时计算值:
def fibonacci():a, b = 0, 1while True:yield aa, b = b, a + b# 获取斐波那契数列的前10个数
fib_gen = fibonacci()
fibs = [next(fib_gen) for _ in range(10)]
print(fibs) # 输出: [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
3. 数据流处理和管道
生成器可用于创建数据处理管道,每个生成器负责一个处理步骤:
def read_data(file_path):with open(file_path, 'r') as file:for line in file:yield line.strip()def parse_data(lines):for line in lines:# 解析每行数据yield line.split(',')def filter_data(parsed_lines):for items in parsed_lines:if len(items) >= 3 and items[2].isdigit():yield items# 构建数据处理管道
lines = read_data('data.csv')
parsed = parse_data(lines)
filtered = filter_data(parsed)for item in filtered:print(item)
4. 提高内存效率
对比处理100万个数的平方的两种方式:
# 使用列表 - 占用大量内存
squares_list = [i ** 2 for i in range(1000000)]
sum_squares = sum(squares_list)# 使用生成器 - 内存高效
sum_squares = sum(i ** 2 for i in range(1000000))
生成器表达式与列表推导式的区别
特性 | 生成器表达式 | 列表推导式 |
---|---|---|
语法 | 使用圆括号 (x for x in range(10)) | 使用方括号 [x for x in range(10)] |
内存使用 | 惰性计算,内存高效 | 一次性创建所有元素,占用更多内存 |
计算时机 | 按需计算 | 立即计算所有值 |
访问方式 | 只能顺序访问,不能索引 | 可以通过索引随机访问 |
重复使用 | 只能遍历一次 | 可以多次遍历 |
速度 | 生成值时可能稍慢,但初始化快 | 初始化慢,但访问值更快 |
示例:
# 列表推导式
list_comp = [x ** 2 for x in range(5)]
print(list_comp) # 输出: [0, 1, 4, 9, 16]
print(list_comp[2]) # 输出: 4
print(sum(list_comp)) # 输出: 30
print(sum(list_comp)) # 还可以再次使用,输出: 30# 生成器表达式
gen_expr = (x ** 2 for x in range(5))
# print(gen_expr[2]) # 错误:生成器不支持索引访问
print(sum(gen_expr)) # 输出: 30
print(sum(gen_expr)) # 输出: 0 (生成器已耗尽,不能重用)
常见问题与陷阱
1. 生成器耗尽后不能重新使用
一旦生成器被完全迭代,它就不能被重置或重新使用:
def count_to_three():yield 1yield 2yield 3gen = count_to_three()
print(list(gen)) # 输出: [1, 2, 3]
print(list(gen)) # 输出: [] (生成器已耗尽)
2. 在生成器上使用列表函数会消耗生成器
将生成器转换为列表会消耗生成器:
gen = (x for x in range(5))
data = list(gen) # 将生成器转换为列表
print(data) # 输出: [0, 1, 2, 3, 4]
print(list(gen)) # 输出: [] (生成器已被消耗)
3. 生成器的延迟绑定
在生成器中使用循环变量时要小心延迟绑定:
# 错误的做法
funcs = [lambda: i for i in range(5)]
results = [f() for f in funcs]
print(results) # 输出: [4, 4, 4, 4, 4]# 正确的做法
funcs = [(lambda j=i: j) for i in range(5)]
results = [f() for f in funcs]
print(results) # 输出: [0, 1, 2, 3, 4]
总结
- 生成器是一种特殊的迭代器,能够按需生成值,节省内存
- 使用
yield
关键字创建生成器函数,或使用生成器表达式 - 生成器在每次调用时暂停执行并保存状态
- 生成器特别适合处理大型数据集、无限序列和数据流
- 与列表不同,生成器只能前进不能后退,且不支持索引访问
- 生成器表达式比列表推导式更节省内存,但只能遍历一次
- 使用生成器能显著提高程序的内存效率和可扩展性