Python 生成器与生成器表达式详解

张开发
2026/4/18 20:30:11 15 分钟阅读

分享文章

Python 生成器与生成器表达式详解
生成器是 Python 中实现惰性求值的核心机制允许我们在迭代过程中按需生成值而不是一次性创建所有结果。这种特性在处理大数据流、无限序列或构建管道式数据处理时极为有用。1. 生成器的本质生成器是一种特殊的迭代器它实现了迭代器协议__iter__和__next__方法但与普通迭代器不同的是生成器使用yield语句来暂停函数并保留状态下次调用__next__()时从暂停处继续执行。关键特性惰性计算仅当请求时才生成下一个值内存友好不需要像列表那样存储所有元素单次遍历生成器只能前进不能回退自动状态管理局部变量、指令指针等在每次yield后保留2. 生成器函数yield通过yield定义生成器函数调用时返回生成器对象。2.1 基本用法defcount_up_to(n):生成从 1 到 n 的整数i1whilein:yieldi i1# 调用生成器函数返回生成器对象函数体不会立即执行gencount_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 32.2 yield vs return特性returnyield函数行为终止函数返回单一值生成一个值并挂起函数多次调用每次从头执行从上次 yield 处恢复执行返回值类型任意对象函数直接返回生成器对象需迭代获取内存占用可能较大如返回列表极小仅保存当前状态defwith_return():return[1,2,3]# 一次性返回整个列表defwith_yield():yield1yield2yield3# 逐个产生# 类型不同print(type(with_return()))# class listprint(type(with_yield()))# class generator2.3 多个 yield 和状态保留生成器会记住每个yield之间的局部变量和代码位置deffibonacci(limit):生成不超过 limit 的斐波那契数列a,b0,1whilealimit:yielda a,bb,abfornuminfibonacci(100):print(num,end )# 0 1 1 2 3 5 8 13 21 34 55 892.4 生成器对象的方法生成器除了__next__()外还提供三个高级方法.send(value)恢复执行并向生成器传递一个值该值成为当前yield表达式的返回值。.throw(type, valueNone, tracebackNone)在生成器暂停处抛出一个异常。.close()停止生成器内部会引发GeneratorExit异常。defecho():接收发送的值并返回whileTrue:receivedyieldprint(fReceived:{received})genecho()next(gen)# 预激生成器运行到第一个 yield 之前gen.send(Hello)# 发送值yield 收到 Hello输出 Received: Hellogen.send(World)# 同样# gen.close() # 关闭生成器# 带有初始 yield 值的例子defaccumulator():total0whileTrue:valueyieldtotal# yield 当前总和同时接收新值ifvalueisNone:breaktotalvaluereturntotal accaccumulator()print(next(acc))# 0 (首次调用total 初始 0)print(acc.send(10))# 10print(acc.send(20))# 30try:acc.send(None)# 触发 breakStopIteration 携带 return 值exceptStopIterationase:print(fFinal total:{e.value})# 输出 302.5 生成器的闭包特性生成器函数可以引用外部变量形成闭包defmake_generator(start):defgen():nstartwhileTrue:yieldn n1returngen()gmake_generator(10)print(next(g))# 10print(next(g))# 113. 生成器表达式生成器表达式提供了一种简洁的惰性求值语法类似于列表推导式但使用圆括号()而非方括号[]。3.1 基本语法# 列表推导式立即计算所有值squares_list[x**2forxinrange(10)]# 占用内存存储 10 个整数# 生成器表达式惰性计算squares_gen(x**2forxinrange(10))# 返回生成器对象print(type(squares_gen))# class generatorprint(next(squares_gen))# 0print(next(squares_gen))# 1# ... 依次产生3.2 与列表推导式的内存对比importsys list_comp[iforiinrange(1000000)]gen_exp(iforiinrange(1000000))print(fList size:{sys.getsizeof(list_comp)}bytes)# 约 8 MB (取决于 Python 版本)print(fGenerator size:{sys.getsizeof(gen_exp)}bytes)# 通常 112 字节左右3.3 嵌套与条件过滤生成器表达式支持if子句和多重循环# 条件过滤even_squares(x**2forxinrange(20)ifx%20)# 多重循环pairs((x,y)forxinrange(3)foryinrange(3))# 可以组合使用fora,binpairs:print(a,b)3.4 链式生成器表达式多个生成器表达式可以串联形成数据处理管道每个步骤都是惰性执行的numbersrange(10)# 步骤1: 乘以2doubled(n*2forninnumbers)# 步骤2: 过滤 5filtered(nfornindoubledifn5)# 步骤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():yield1yield2gensimple_gen()print(hasattr(gen,__iter__))# Trueprint(hasattr(gen,__next__))# Trueprint(iter(gen)isgen)# True (生成器自身的迭代器就是自己)4.2 生成器状态GEN_CREATED, GEN_RUNNING, GEN_SUSPENDED, GEN_CLOSEDfrominspectimportgetgeneratorstatedefdemo():yield1yield2gdemo()print(getgeneratorstate(g))# GEN_CREATEDnext(g)print(getgeneratorstate(g))# GEN_SUSPENDEDnext(g)print(getgeneratorstate(g))# GEN_CLOSED4.3 yield from 语法Python 3.3yield 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,encodingutf-8)asf:forlineinf:yieldline.strip()# 处理 10GB 的日志文件forlineinread_large_file(huge_log.txt):ifERRORinline:print(line)5.2 生成无限序列definfinite_primes():无限素数生成器使用埃拉托斯特尼筛法变种yield2primes[]n3whileTrue:is_primeTrueforpinprimes:ifp*pn:breakifn%p0:is_primeFalsebreakifis_prime:primes.append(n)yieldn n2# 取前 10 个素数fromitertoolsimportislice first_10_primeslist(islice(infinite_primes(),10))print(first_10_primes)# [2, 3, 5, 7, 11, 13, 17, 19, 23, 29]5.3 数据管道ETLdefread_data(source):forrecordinsource:yieldrecorddefclean_data(records):forrecinrecords:# 清洗逻辑去除空格、转换类型等cleanedrec.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]pipelineread_data(source)pipelineclean_data(pipeline)pipelinetransform_data(pipeline)load_data(pipeline,output.csv)# 整个过程一次处理一行内存占用恒定5.4 实现协程简易任务调度deftask(name,times):foriinrange(times):print(fTask{name}running step{i})yield# 让出控制权defscheduler(tasks):taskslist(tasks)# 保存所有协程对象whiletasks:tasktasks.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# 列表预先计算所有平方starttime.time()result[x**2forxinrange(10000000)]print(fList time:{time.time()-start:.2f}s, memory: ~{len(result)*28/1024/1024:.1f}MB)# 生成器惰性计算几乎不占内存starttime.time()gen(x**2forxinrange(10000000))# 此时仅创建了生成器对象没有计算任何平方print(fGen creation time:{time.time()-start:.2f}s)# 使用生成器例如求和totalsum(gen)# 此时才逐个计算print(fGen 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生成器都是不可或缺的利器。

更多文章