深入理解协程(二):yield from实现异步协程
原创不易,转载请联系作者
深入理解协程分为三部分进行讲解:
- 协程的引入
- yield from实现异步协程
- async/await实现异步协程
本篇为深入理解协程系列文章的第二篇。
yield from
yield from是Python3.3(PEP 380)引入的新语法。主要用于解决在生成器中不方便使用生成器的问题。主要有两个功能。
第一个功能:让嵌套生成器不必再通过循环迭代yield,而可以直接使用yield from。
看一段代码:
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减少了一次循环。
第二个功能:打开双向通道,把最外层给调用方与最内层的子生成器链接起来,二者可以直接通信。
第二个功能听起来就让人头大。我们再举一个例子进行说明:
【举个例子】:通过生成器实现整数相加,通过
send()函数想生成器中传入要相加的数字,最后传入None结束相加。total保存结果。
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实现异步协程
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)。
执行过程:
- 先通过get_event_loop()获取了一个标准事件循环loop(因为是一个,所以协程是单线程)
- 然后,我们通过run_until_complete(main())来运行协程(此处把调用方协程main()作为参数,调用方负责调用其他委托生成器),run_until_complete的特点就像该函数的名字,直到循环事件的所有事件都处理完才能完整结束.
- 进入调用方协程,我们把多个任务[taskIO_1()和taskIO_2()]放到一个task列表中,可理解为打包任务。
- 我们使用asyncio.wait(tasks)来获取一个awaitable objects即可等待对象的集合,通过yield from返回一个包含(done, pending)的元组,done表示已完成的任务列表,pending表示未完成的任务列表。
- 因为done里面有我们需要的返回结果,但它目前还是个任务列表,所以要取出返回的结果值,我们遍历它并逐个调用result()取出结果即可。
- 最后我们通过loop.close()关闭事件循环。
可见,通过使用协程,极大提高了多任务的执行效率,程序最后消耗的时间是任务队列中耗时最多时间任务的时长。
总结
本篇讲述了:
yield from如何实现协程- 如何结合
asyncio实现异步协程
虽然有了yield from的存在,让协程实现比之前容易了,但是这种异步协程的实现方式,并不是很pythonic。现在已经不推荐使用了。下篇将与您分享更加完善的Python异步实现方式——async/await实现异步协程。
参考
Python异步IO之协程(一):从yield from到async的使用
关注公众号西加加先生一起玩转Python。

深入理解协程(二):yield from实现异步协程的更多相关文章
- Python用yield form 实现异步协程爬虫
很古老的用法了,现在大多用的aiohttp库实现,这篇记录仅仅用做个人的协程底层实现的学习. 争取用看得懂的字来描述问题. 1.什么是yield 如果还没有怎么用过的话,直接把yield看做成一种特殊 ...
- 深入理解协程(三):async/await实现异步协程
原创不易,转载请联系作者 深入理解协程分为三部分进行讲解: 协程的引入 yield from实现异步协程 async/await实现异步协程 本篇为深入理解协程系列文章的最后一篇. 从本篇你将了解到: ...
- 异步协程asyncio+aiohttp
aiohttp中文文档 1. 前言 在执行一些 IO 密集型任务的时候,程序常常会因为等待 IO 而阻塞.比如在网络爬虫中,如果我们使用 requests 库来进行请求的话,如果网站响应速度过慢,程序 ...
- Python中异步协程的使用方法介绍
1. 前言 在执行一些 IO 密集型任务的时候,程序常常会因为等待 IO 而阻塞.比如在网络爬虫中,如果我们使用 requests 库来进行请求的话,如果网站响应速度过慢,程序一直在等待网站响应,最后 ...
- 理解Python协程:从yield/send到yield from再到async/await
Python中的协程大概经历了如下三个阶段:1. 最初的生成器变形yield/send2. 引入@asyncio.coroutine和yield from3. 在最近的Python3.5版本中引入as ...
- 5)协程二(yeild from)
一:yield from说明 python从3.3版本开始使用yield from 替代yield yield from 结构会在内部自动捕获 StopIteration 异常. 这种处理方式与 ...
- yield、greenlet与协程gevent
yield 在说明yield之前,我们了解python中一些概念. 在了解Python的数据结构时,容器(container).可迭代对象(iterable).迭代器(iterator).生成器(ge ...
- Python进阶----异步同步,阻塞非阻塞,线程池(进程池)的异步+回调机制实行并发, 线程队列(Queue, LifoQueue,PriorityQueue), 事件Event,线程的三个状态(就绪,挂起,运行) ,***协程概念,yield模拟并发(有缺陷),Greenlet模块(手动切换),Gevent(协程并发)
Python进阶----异步同步,阻塞非阻塞,线程池(进程池)的异步+回调机制实行并发, 线程队列(Queue, LifoQueue,PriorityQueue), 事件Event,线程的三个状态(就 ...
- 再议Python协程——从yield到asyncio
协程,英文名Coroutine.前面介绍Python的多线程,以及用多线程实现并发(参见这篇文章[浅析Python多线程]),今天介绍的协程也是常用的并发手段.本篇主要内容包含:协程的基本概念.协程库 ...
随机推荐
- Jieba分词包(一)——解析主函数cut
1. 解析主函数cut Jieba分词包的主函数在jieba文件夹下的__init__.py中,在这个py文件中有个cut的函数,这个就是控制着整个jieba分词包的主函数. cut函数的定义如 ...
- HDU 2546 01背包问题
这里5元是个什么意思呢.差不多就是特殊情况了. 就是说最贵的那个东西先不买.并且最后要留下5元去买那个最贵的. 也就是说对现在金钱-5 拿剩下的钱去对减去最贵的商品后的商品dp.看这些剩下的钱能买多少 ...
- UA判断打开页面的环境,然后在callBack写相应环境下的回调函数
这是js代码 /* * 2016.11.10 * SunJingxin * V 1.0.0 * */ (function(){ /* * 使用方法: * 一.引入ua.js * 二.直接调用 Mobi ...
- Python--day41--守护线程
1,守护线程:守护线程会在主线程结束之后等待其他子线程的结束才结束 拓展--守护进程:守护进程随着主进程代码的执行结束而结束 代码示例:守护线程.py import time from threadi ...
- JOISC2014 挂饰("01"背包)
传送门: [1]:洛谷 [2]:BZOJ 参考资料: [1]:追忆:往昔 •题解 上述参考资料的讲解清晰易懂,下面谈谈我的理解: 关键语句: 将此题转化为 "01背包" 类问题,关 ...
- 安装 Sureface Hub 系统 Windows 10 team PPIPro 系统
本文告诉大家如何安装这个系统 本文的方法我自己没试过,如果失败了,不要打我 下载地址 中文版 https://pan.baidu.com/s/1gAJSSE6KB9JHXo4BT_VfmA 其他请看 ...
- Filter、Intercepter、AOP的区别
在使用Spring MVC开发RESTful API的时候,我们经常会使用Java的拦截机制来处理请求,Filter是Java本身自带拦过滤器,Interceptor则是Spring自带的拦截器,而A ...
- vue组件之间通过query传递参数
需求: 从 任务列表进入 任务详情 ,向详情页传递当前 mission_id 值 路由关系: //查看任务列表 { path: '/worklist', name: 'worklist', compo ...
- Javascript中那些你不知道的事之-- false、0、null、undefined和空字符串
话不多说直接进入主题:(如果有写的不对的地方欢迎指正) 我们先来看看他们的类型分别是什么: typeof类型检测结果 结论:false是布尔类型对象,0是数字类型对象,null是object对象,un ...
- PowerShell 使用 WMI 获取信息
在 PowerShell 可以很容易使用 WMI 拿到系统的信息,如果有关注我的网站,就会发现我写了很多通过 WMI 拿到系统的显卡,系统安装的软件等方法,本文告诉大家如果通过 PowerShell ...