Python 生成器与生成器表达式详解
2026/4/18 20:30:05 网站建设 项目流程

生成器是 Python 中实现惰性求值的核心机制,允许我们在迭代过程中按需生成值,而不是一次性创建所有结果。这种特性在处理大数据流、无限序列或构建管道式数据处理时极为有用。

1. 生成器的本质

生成器是一种特殊的迭代器,它实现了迭代器协议(__iter____next__方法),但与普通迭代器不同的是,生成器使用yield语句来暂停函数并保留状态,下次调用__next__()时从暂停处继续执行。

关键特性:

  • 惰性计算:仅当请求时才生成下一个值
  • 内存友好:不需要像列表那样存储所有元素
  • 单次遍历:生成器只能前进,不能回退
  • 自动状态管理:局部变量、指令指针等在每次yield后保留

2. 生成器函数(yield)

通过yield定义生成器函数,调用时返回生成器对象。

2.1 基本用法
defcount_up_to(n):"""生成从 1 到 n 的整数"""i=1whilei<=n:yieldi i+=1# 调用生成器函数返回生成器对象,函数体不会立即执行gen=count_up_to(3)print(gen)# <generator object count_up_to at 0x...># 通过 next() 获取值print(next(gen))# 1print(next(gen))# 2print(next(gen))# 3# print(next(gen)) # 抛出 StopIteration# 或者用 for 循环自动捕获 StopIterationforvalueincount_up_to(3):print(value)# 输出 1 2 3
2.2 yield vs return
特性returnyield
函数行为终止函数,返回单一值生成一个值并挂起函数
多次调用每次从头执行从上次 yield 处恢复执行
返回值类型任意对象(函数直接返回)生成器对象(需迭代获取)
内存占用可能较大(如返回列表)极小(仅保存当前状态)
defwith_return():return[1,2,3]# 一次性返回整个列表defwith_yield():yield1yield2yield3# 逐个产生# 类型不同print(type(with_return()))# <class 'list'>print(type(with_yield()))# <class 'generator'>
2.3 多个 yield 和状态保留

生成器会记住每个yield之间的局部变量和代码位置:

deffibonacci(limit):"""生成不超过 limit 的斐波那契数列"""a,b=0,1whilea<=limit:yielda a,b=b,a+bfornuminfibonacci(100):print(num,end=' ')# 0 1 1 2 3 5 8 13 21 34 55 89
2.4 生成器对象的方法

生成器除了__next__()外,还提供三个高级方法:

  • .send(value):恢复执行并向生成器传递一个值,该值成为当前yield表达式的返回值。
  • .throw(type, value=None, traceback=None):在生成器暂停处抛出一个异常。
  • .close():停止生成器,内部会引发GeneratorExit异常。
defecho():"""接收发送的值并返回"""whileTrue:received=yieldprint(f"Received:{received}")gen=echo()next(gen)# 预激生成器,运行到第一个 yield 之前gen.send("Hello")# 发送值,yield 收到 "Hello",输出 Received: Hellogen.send("World")# 同样# gen.close() # 关闭生成器# 带有初始 yield 值的例子defaccumulator():total=0whileTrue:value=yieldtotal# yield 当前总和,同时接收新值ifvalueisNone:breaktotal+=valuereturntotal acc=accumulator()print(next(acc))# 0 (首次调用,total 初始 0)print(acc.send(10))# 10print(acc.send(20))# 30try:acc.send(None)# 触发 break,StopIteration 携带 return 值exceptStopIterationase:print(f"Final total:{e.value}")# 输出 30
2.5 生成器的闭包特性

生成器函数可以引用外部变量,形成闭包:

defmake_generator(start):defgen():n=startwhileTrue:yieldn n+=1returngen()g=make_generator(10)print(next(g))# 10print(next(g))# 11

3. 生成器表达式

生成器表达式提供了一种简洁的惰性求值语法,类似于列表推导式,但使用圆括号()而非方括号[]

3.1 基本语法
# 列表推导式(立即计算所有值)squares_list=[x**2forxinrange(10)]# 占用内存存储 10 个整数# 生成器表达式(惰性计算)squares_gen=(x**2forxinrange(10))# 返回生成器对象print(type(squares_gen))# <class 'generator'>print(next(squares_gen))# 0print(next(squares_gen))# 1# ... 依次产生
3.2 与列表推导式的内存对比
importsys list_comp=[iforiinrange(1000000)]gen_exp=(iforiinrange(1000000))print(f"List size:{sys.getsizeof(list_comp)}bytes")# 约 8 MB (取决于 Python 版本)print(f"Generator size:{sys.getsizeof(gen_exp)}bytes")# 通常 112 字节左右
3.3 嵌套与条件过滤

生成器表达式支持if子句和多重循环:

# 条件过滤even_squares=(x**2forxinrange(20)ifx%2==0)# 多重循环pairs=((x,y)forxinrange(3)foryinrange(3))# 可以组合使用fora,binpairs:print(a,b)
3.4 链式生成器表达式

多个生成器表达式可以串联,形成数据处理管道,每个步骤都是惰性执行的:

numbers=range(10)# 步骤1: 乘以2doubled=(n*2forninnumbers)# 步骤2: 过滤 > 5filtered=(nfornindoubledifn>5)# 步骤3: 转为字符串strs=(str(n)forninfiltered)forsinstrs:print(s,end=' ')# 输出: 6 8 10 12 14 16 18# 整个过程不会创建中间列表,一次只处理一个元素

4. 深入理解生成器的工作机制

4.1 迭代器协议实现

生成器对象自动实现了__iter____next__,因此可以直接用于for循环、list()sum()等接受可迭代对象的函数。

defsimple_gen():yield1yield2gen=simple_gen()print(hasattr(gen,'__iter__'))# Trueprint(hasattr(gen,'__next__'))# Trueprint(iter(gen)isgen)# True (生成器自身的迭代器就是自己)
4.2 生成器状态(GEN_CREATED, GEN_RUNNING, GEN_SUSPENDED, GEN_CLOSED)
frominspectimportgetgeneratorstatedefdemo():yield1yield2g=demo()print(getgeneratorstate(g))# GEN_CREATEDnext(g)print(getgeneratorstate(g))# GEN_SUSPENDEDnext(g)print(getgeneratorstate(g))# GEN_CLOSED
4.3 yield from 语法(Python 3.3+)

yield from可以将迭代任务委托给另一个生成器(或任何可迭代对象),简化嵌套生成器的编写。

defsub_generator():yield1yield2defmain_generator():yield0yieldfromsub_generator()# 等同于 for v in sub_generator(): yield vyield3forvalinmain_generator():print(val)# 0 1 2 3# 递归遍历树状结构defflatten(nested):foriteminnested:ifisinstance(item,(list,tuple)):yieldfromflatten(item)else:yielditem nested_list=[1,[2,[3,4],5],6]print(list(flatten(nested_list)))# [1, 2, 3, 4, 5, 6]

5. 实战应用示例

5.1 处理超大文件(逐行读取)
defread_large_file(file_path):"""惰性读取大文件,不将整个文件加载到内存"""withopen(file_path,'r',encoding='utf-8')asf:forlineinf:yieldline.strip()# 处理 10GB 的日志文件forlineinread_large_file('huge_log.txt'):if'ERROR'inline:print(line)
5.2 生成无限序列
definfinite_primes():"""无限素数生成器(使用埃拉托斯特尼筛法变种)"""yield2primes=[]n=3whileTrue:is_prime=Trueforpinprimes:ifp*p>n:breakifn%p==0:is_prime=Falsebreakifis_prime:primes.append(n)yieldn n+=2# 取前 10 个素数fromitertoolsimportislice first_10_primes=list(islice(infinite_primes(),10))print(first_10_primes)# [2, 3, 5, 7, 11, 13, 17, 19, 23, 29]
5.3 数据管道(ETL)
defread_data(source):forrecordinsource:yieldrecorddefclean_data(records):forrecinrecords:# 清洗逻辑:去除空格、转换类型等cleaned=rec.strip().lower()ifcleaned:yieldcleaneddeftransform_data(records):forrecinrecords:yieldrec.split(',')defload_data(records,output_file):withopen(output_file,'w')asf:forrecinrecords:f.write(','.join(rec)+'\n')# 组装管道source=[' Alice, 30\n','Bob,25\n',' Charlie, 35\n']pipeline=read_data(source)pipeline=clean_data(pipeline)pipeline=transform_data(pipeline)load_data(pipeline,'output.csv')# 整个过程一次处理一行,内存占用恒定
5.4 实现协程(简易任务调度)
deftask(name,times):foriinrange(times):print(f"Task{name}running step{i}")yield# 让出控制权defscheduler(tasks):tasks=list(tasks)# 保存所有协程对象whiletasks:task=tasks.pop(0)try:next(task)# 执行一步tasks.append(task)# 放回队列尾部exceptStopIteration:pass# 创建任务tasks=[task('A',3),task('B',2),task('C',4)]scheduler(tasks)# 输出交替执行的任务步骤

6. 性能与最佳实践

6.1 生成器 vs 列表的性能对比
importtime# 列表:预先计算所有平方start=time.time()result=[x**2forxinrange(10000000)]print(f"List time:{time.time()-start:.2f}s, memory: ~{len(result)*28/1024/1024:.1f}MB")# 生成器:惰性计算,几乎不占内存start=time.time()gen=(x**2forxinrange(10000000))# 此时仅创建了生成器对象,没有计算任何平方print(f"Gen creation time:{time.time()-start:.2f}s")# 使用生成器(例如求和)total=sum(gen)# 此时才逐个计算print(f"Gen sum time:{time.time()-start:.2f}s")

何时使用生成器?

  • 数据量巨大,无法全部放入内存
  • 只需遍历一次(不需要索引、切片等随机访问)
  • 希望延迟计算,提高响应性
  • 构建可组合的数据处理管道

何时使用列表?

  • 数据量小或需要多次遍历
  • 需要随机访问(索引、切片)
  • 需要修改元素
  • 计算开销极小且会立即使用所有元素
6.2 生成器表达式的陷阱
# 生成器只能使用一次gen=(xforxinrange(5))print(list(gen))# [0,1,2,3,4]print(list(gen))# [],已耗尽# 不要在生成器表达式中使用可变对象作为默认值(与函数默认参数类似问题)# 正确做法:将生成器表达式立即转换为列表,或者使用明确的循环

7. 总结

特性生成器函数生成器表达式
语法def func(): yield ...(expr for var in iterable)
适用场景复杂逻辑、状态保持、多步计算简单映射/过滤、单行表达式
可读性函数形式更清晰(多步时)简洁(类似列表推导式)
支持yield from
支持send/throw否(生成器表达式产生的对象无这些方法)

生成器和生成器表达式是 Python 惰性编程的核心工具,掌握它们可以写出更高效、更优雅的代码。从处理 TB 级日志到实现流式 API,生成器都是不可或缺的利器。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询