大兴安岭地区网站建设_网站建设公司_自助建站_seo优化
2025/12/17 12:43:38 网站建设 项目流程

生成器:惰性求值与协程基础

生成器让你能够按需产生值,而不是一次性在内存中构建整个序列,这对于处理大型或无限的数据流至关重要 。

生成器函数与yield

任何包含yield关键字的函数都是一个生成器函数。调用它时,会返回一个生成器对象。

defcountdown(n):print("Starting countdown from",n)whilen>0:yieldn# 每次执行到yield时暂停,返回n的值n-=1# 创建生成器对象counter=countdown(3)print(next(counter))# 输出: Starting countdown from 3 \n 3print(next(counter))# 输出: 2print(next(counter))# 输出: 1# print(next(counter)) # 再调用会抛出StopIteration异常

生成器表达式

类似于列表推导,但使用圆括号,并且返回一个生成器 。

# 列表推导:立即生成所有数据,占用内存list_comp=[x*xforxinrange(1000000)]# 生成器表达式:按需生成数据,节省内存gen_exp=(x*xforxinrange(1000000))

协程:生成器的双向通信

生成器不仅可以产出值,还可以通过.send(value)方法接收值,这使其成为协程的基础 。

defaverager():total=0.0count=0whileTrue:new_value=yieldtotal# yield表达式可以接收外部send进来的值ifnew_valueisNone:# 通常用None作为哨兵值来终止协程breaktotal+=new_value count+=1ifcount>0:total=total/count# 计算平均值avg_cor=averager()next(avg_cor)# 预激(prime)协程,使其运行到第一个yield处暂停print(avg_cor.send(10))# 发送10,产出平均值10.0print(avg_cor.send(20))# 发送20,产出平均值15.0

send()如何工作

理解 send()方法,关键在于明白它和 yield关键字的关系。

  1. yield的双重角色:yield不仅用于产出值(向生成器外部),其本身也是一个可以接收值的表达式(从生成器外部)。当生成器执行到 yield语句暂停时,它实际上在等待一个值。

  2. send(value)的职责:send(value)方法做两件事:
    传值:将参数 value发送到生成器内部,这个值会成为当前暂停的yield表达式的结果
    恢复执行:恢复生成器的运行,直到下一个 yield或函数结束。
    与只能恢复执行的 next()方法相比,send()的核心优势在于它能传递数据到生成器内部

defsimple_coroutine():print("-> 协程启动")# yield 表达式在这里暂停,并等待接收一个值x=yield"产出值: 1"print(f"-> 协程接收到了:{x}")y=yield"产出值: 2"print(f"-> 协程接收到了:{y}")print("-> 协程结束")# 创建生成器对象coro=simple_coroutine()# 第一步:必须使用 next() 或 send(None) 来"启动"或"预激"生成器,使其运行到第一个 yield 处暂停。# 此时生成器产出 "产出值: 1"first_yield=next(coro)print(f"主程序收到:{first_yield}")# 第二步:使用 send 发送数据# send("数据A") 会恢复生成器,并将 "数据A" 赋值给上一个 yield 表达式(即 x = yield ... 中的 yield)# 生成器继续执行,打印出接收到的数据,运行到下一个 yield 处暂停,并产出 "产出值: 2"second_yield=coro.send("数据A")print(f"主程序收到:{second_yield}")# 第三步:再次发送数据# 此时将 "数据B" 发送给生成器,赋值给 ytry:coro.send("数据B")exceptStopIteration:print("生成器已执行完毕。")

-> 协程启动
主程序收到: 产出值: 1
-> 协程接收到了: 数据A
主程序收到: 产出值: 2
-> 协程接收到了: 数据B
-> 协程结束
生成器已执行完毕。

其他例子

维护状态的累加器:生成器可以成为一个有记忆的累加器

defaccumulator():total=0whileTrue:# 每次 send 来的值都会累加到 total 上,并返回当前总和value=yieldtotalifvalueisnotNone:total+=value acc=accumulator()next(acc)# 预激,此时 total=0print(acc.send(5))# 输出: 5 (0+5)print(acc.send(10))# 输出: 15 (5+10)

实现简单的协程与任务调度:通过 send()可以在多个生成器之间传递数据和控制权,实现协作式的多任务。例如,一个简单的生产者-消费者模型:

defconsumer():whileTrue:item=yield# 等待生产者发送数据print(f'消费:{item}')defproducer(cons):cons.send(None)# 预激消费者协程foriinrange(3):print(f'生产:{i}')cons.send(i)# 将数据发送给消费者c=consumer()producer(c)

为异步编程奠定基础:send()方法是 Python 中现代异步框架(如 asyncio)早期实现的基础。执行器(Runner)可以利用 send()来协调多个异步任务的执行。


总而言之,可以这样理解:Python最新的协程在语法和API层面已经是一个自成一体的独立概念,但其实现的核心思想——利用“暂停与恢复”来高效处理I/O——其技术根源确实来自于生成器。​ 您可以认为生成器是协程的“底层引擎”或“祖先”,而 async/await是建立在它之上的一套更优雅、更强大的“上层建筑”和“操作界面”。

注意事项

yield from

简化生成器嵌套

最基本的功能是扁平化处理可迭代对象。对比一下使用 yield和 yield from的代码:

# 使用 yield 的繁琐方式defchain_with_yield(*iterables):foritiniterables:foriinit:yieldi# 使用 yield from 的简洁方式defchain_with_yield_from(*iterables):foritiniterables:yieldfromit# 直接委托给子迭代器# 效果完全相同list(chain_with_yield('AB',[1,2]))# 输出: ['A', 'B', 1, 2]list(chain_with_yield_from('AB',[1,2]))# 输出: ['A', 'B', 1, 2]

📞 建立双向通信

yield from更强大的功能是打开一个双向通道,让调用方(外部代码)和子生成器能直接通信,包括使用 .send()方法传值和 .throw()方法抛异常。委托生成器在此主要起连接作用 。

defsub_generator():"""子生成器,真正处理业务逻辑"""total=0count=0whileTrue:try:new_num=yieldtotal# 等待接收数据,并返回当前总和exceptValueError:print("子生成器捕获到异常")breakifnew_numisNone:breakcount+=1total+=new_numreturntotal,count# 生成器的返回值defdelegating_generator():"""委托生成器,使用yield from连接调用方和子生成器"""# yield from 表达式的结果是子生成器的返回值result=yieldfromsub_generator()print(f"子生成器返回的结果:{result}")# 调用方delegator=delegating_generator()next(delegator)# 预激,使代码运行到子生成器的yield处暂停print(delegator.send(10))# 输出:10. 值10直接传给子生成器,收到当前总和10print(delegator.send(20))# 输出:30. 值20直接传给子生成器,收到当前总和30delegator.throw(ValueError)# 在子生成器暂停处抛出异常,子生成器捕获并处理# 输出:# 子生成器捕获到异常# 子生成器返回的结果:(30, 2)


yield from的核心价值在于简化生成器嵌套,并创建调用方与子生成器之间的双向通信通道。它自动处理了迭代、值传递和异常处理等繁琐细节,尤其在实现协程或复杂的数据流管道时非常有用。

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

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

立即咨询