> 技术文档 > python 函数—生成器_python 生成器

python 函数—生成器_python 生成器


Python 生成器(Generator)

目录

  1. 什么是生成器
  2. 生成器语法
  3. 生成器的工作原理
  4. 使用方法
    • 通过next()函数
    • 通过for循环
    • 其他方法
  5. 应用场景
  6. 生成器表达式与列表推导式的区别
  7. 常见问题与陷阱

什么是生成器

生成器(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))

生成器的工作原理

生成器的核心机制是函数执行的暂停与恢复:

  1. 当调用生成器函数时,返回一个生成器对象,但函数体不执行
  2. 当第一次调用next()方法时,函数开始执行,直到遇到yield语句
  3. yield语句返回一个值,并且函数执行暂停,保存所有当前的状态(局部变量等)
  4. 下一次调用next()方法时,函数从上次暂停的地方继续执行
  5. 当函数执行完毕时,生成器抛出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

其他方法

生成器对象还有其他高级方法:

  1. 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(\"你好\")) # 发送值并获取下一个值,输出: 你发送了: 你好
  1. 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!
  1. 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关键字创建生成器函数,或使用生成器表达式
  • 生成器在每次调用时暂停执行并保存状态
  • 生成器特别适合处理大型数据集、无限序列和数据流
  • 与列表不同,生成器只能前进不能后退,且不支持索引访问
  • 生成器表达式比列表推导式更节省内存,但只能遍历一次
  • 使用生成器能显著提高程序的内存效率和可扩展性