原创不易,转载请联系做者python
深刻理解协程
分为三部分进行讲解:并发
本篇为深刻理解协程
系列文章的第二篇。框架
yield from
是Python3.3(PEP 380)引入的新语法。主要用于解决在生成器中不方便使用生成器的问题。主要有两个功能。异步
第一个功能:让嵌套生成器没必要再经过循环迭代yield
,而能够直接使用yield from
。async
看一段代码:函数
titles = ['Python', 'Java', 'C++'] def func1(titles): yield titles def func2(titles): yield from titles for title in func1(titles): print(title) for title in func2(titles): print(title) # 输出结果 ['Python', 'Java', 'C++'] Python Java C++
yield
返回的完整的titles
列表,而yield from
返回的是列表中的具体元素。yield from
能够看做是for title in titles: yield title
的缩写。这样就能够用yield from
减小了一次循环。oop
第二个功能:打开双向通道,把最外层给调用方与最内层的子生成器连接起来,两者能够直接通讯。.net
第二个功能听起来就让人头大。咱们再举一个例子进行说明:线程
【举个例子】:经过生成器实现整数相加,经过
send()
函数想生成器中传入要相加的数字,最后传入None
结束相加。total
保存结果。code
def generator_1(): # 子生成器 total = 0 while True: x = yield # 解释4 print(f'+ {x}') if not x: break total += x return total # 解释5 def generator_2(): # 委托生成器 while True: total = yield from generator_1() # 解释3 print(f'total: {total}') if __name__ == '__main__': # 调用方 g2 = generator_2() # 解释1 g2.send(None) # 解释2 g2.send(2) # 解释6 g2.send(3) g2.send(None) # 解释7 # 输出结果 + 2 + 3 + None total: 5
说明:
解释1:g2
是调用generator_2()
获得的生成器对象,做为协程使用。
解释2:预激活协程g2
。
解释3:generator_2
接收的值都会通过yield from
处理,经过管道传入generator_1
实例。generator_2
会在yield from
处暂停,等待generator_1
实例传回的值赋值给total
。
解释4:调用方传入的值都会传到这里。
解释5:此处返回的total
正是generator_2()
中解释3处等待返回的值。
解释6:传入2进行计算。
解释7:在计算的结尾传入None
,跳出generator_1()
的循环,结束计算。
说到这里,相信看过《深刻理解协程(一):协程的引入》的朋友应该就容易理解上面这段代码的运行流程了。
借助上面例子,说明一下随yield from
一块儿引入的3个概念:
子生成器
从yield from
获取任务并完成具体实现的生成器。
委派生成器
包含有 yield from表达式的生成器函数。负责给子生成器委派任务。
调用方
指调用委派生成器的客户端代码。
在每次调用send(value)
时,value
不是传递给委派生成器,而是借助yield from
将value
传递给了子生成器的yield
。
asyncio
是Python 3.4 试验性引入的异步I/O框架(PEP 3156),提供了基于协程作异步I/O编写单线程并发代码的基础设施。其核心组件有事件循环(Event Loop)、协程(Coroutine)、任务(Task)、将来对象(Future)以及其余一些扩充和辅助性质的模块。
在引入asyncio
的时候,还提供了一个装饰器@asyncio.coroutine
用于装饰使用了yield from
的函数,以标记其为协程。
在实现异步协程以前,咱们先看一个同步的案例:
import time def taskIO_1(): print('开始运行IO任务1...') time.sleep(2) # 假设该任务耗时2s print('IO任务1已完成,耗时2s') def taskIO_2(): print('开始运行IO任务2...') time.sleep(3) # 假设该任务耗时3s print('IO任务2已完成,耗时3s') start = time.time() taskIO_1() taskIO_2() print('全部IO任务总耗时%.5f秒' % float(time.time()-start)) # 输出结果 开始运行IO任务1... IO任务1已完成,耗时2s 开始运行IO任务2... IO任务2已完成,耗时3s 全部IO任务总耗时5.00094秒
能够看到,使用同步的方式实现多个IO任务的时间是分别执行这两个IO任务时间的总和。
下面咱们使用yield from
与asyncio
将上面的同步代码改为异步的。修改结果以下:
import time import asyncio @asyncio.coroutine # 解释1 def taskIO_1(): print('开始运行IO任务1...') yield from asyncio.sleep(2) # 解释2 print('IO任务1已完成,耗时2s') return taskIO_1.__name__ @asyncio.coroutine def taskIO_2(): print('开始运行IO任务2...') yield from asyncio.sleep(3) # 假设该任务耗时3s print('IO任务2已完成,耗时3s') return taskIO_2.__name__ @asyncio.coroutine def main(): # 调用方 tasks = [taskIO_1(), taskIO_2()] # 把全部任务添加到task中 done,pending = yield from asyncio.wait(tasks) # 子生成器 for r in done: # done和pending都是一个任务,因此返回结果须要逐个调用result() print('协程无序返回值:'+r.result()) if __name__ == '__main__': start = time.time() loop = asyncio.get_event_loop() # 建立一个事件循环对象loop try: loop.run_until_complete(main()) # 完成事件循环,直到最后一个任务结束 finally: loop.close() # 结束事件循环 print('全部IO任务总耗时%.5f秒' % float(time.time()-start)) # 输出结果 开始运行IO任务2... 开始运行IO任务1... IO任务1已完成,耗时2s IO任务2已完成,耗时3s 协程无序返回值:taskIO_1 协程无序返回值:taskIO_2 全部IO任务总耗时3.00303秒
说明:
解释1:@asyncio.coroutine
装饰器是协程函数的标志,咱们须要在每个任务函数前加这个装饰器,并在函数中使用yield from
。
解释2:此处假设该任务运行须要2秒,此处使用异步等待2秒asyncio.sleep(2)
,而非同步等待time.sleep(2)
。
执行过程:
可见,经过使用协程,极大提升了多任务的执行效率,程序最后消耗的时间是任务队列中耗时最多时间任务的时长。
本篇讲述了:
yield from
如何实现协程asyncio
实现异步协程虽然有了yield from
的存在,让协程实现比以前容易了,可是这种异步协程的实现方式,并非很pythonic
。如今已经不推荐使用了。下篇将与您分享更加完善的Python异步实现方式——async/await实现异步协程
。
Python异步IO之协程(一):从yield from到async的使用
关注公众号西加加先生
一块儿玩转Python。