python 函数—生成器_python 生成器
Python 生成器(Generator)
目录
什么是生成器
生成器(Generator)是Python中的一种特殊类型的迭代器(Iterator),它能够按需生成值,而不是一次性生成所有值。与列表、元组等容器类型的可迭代对象不同,生成器不会将所有数据存储在内存中,而是在每次请求时\"即时\"生成数据。
生成器的关键特点:
- 是特殊的迭代器,遵循迭代器协议
- 使用
yield
语句生成值并暂停执行 - 函数状态会被保存,下次调用时从暂停处继续
- 每次调用只提供一个数据
- 不能\"回头\"访问已生成的值
- 支持
next()
函数调用 - 不支持下标索引访问
- 惰性计算,按需生成数据
生成器语法
Python中创建生成器有两种方式:通过生成器函数或生成器表达式。
生成器函数
生成器函数是包含yield
关键字的常规函数。当调用生成器函数时,它返回一个生成器对象,而不执行函数体。
def simple_generator(): \"\"\"一个简单的生成器函数\"\"\" print(\"开始执行\") yield 1 print(\"第一次yield之后\") yield 2 print(\"第二次yield之后\") yield 3 print(\"结束\")# 创建生成器对象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 = 1 while count <= max: yield count count += 1counter = count_up_to(3)print(next(counter)) # 输出: 1print(next(counter)) # 输出: 2print(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 1 yield 2 except ValueError: yield \'Got ValueError!\'g = generator_with_exception()print(next(g)) # 输出: 1print(g.throw(ValueError)) # 输出: Got ValueError!
- close() - 关闭生成器
def closable_generator(): try: yield 1 yield 2 yield 3 finally: print(\"生成器被关闭\")g = closable_generator()print(next(g)) # 输出: 1g.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, 1 while True: yield a a, 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]) # 输出: 4print(sum(list_comp)) # 输出: 30print(sum(list_comp)) # 还可以再次使用,输出: 30# 生成器表达式gen_expr = (x ** 2 for x in range(5))# print(gen_expr[2]) # 错误:生成器不支持索引访问print(sum(gen_expr)) # 输出: 30print(sum(gen_expr)) # 输出: 0 (生成器已耗尽,不能重用)
常见问题与陷阱
1. 生成器耗尽后不能重新使用
一旦生成器被完全迭代,它就不能被重置或重新使用:
def count_to_three(): yield 1 yield 2 yield 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
关键字创建生成器函数,或使用生成器表达式 - 生成器在每次调用时暂停执行并保存状态
- 生成器特别适合处理大型数据集、无限序列和数据流
- 与列表不同,生成器只能前进不能后退,且不支持索引访问
- 生成器表达式比列表推导式更节省内存,但只能遍历一次
- 使用生成器能显著提高程序的内存效率和可扩展性