为什么你还不懂得怎么使用Python协程


Posted in Python onMay 13, 2019

前言

从语法上来看,协程和生成器类似,都是定义体中包含yield关键字的函数。
yield在协程中的用法:

  • 在协程中yield通常出现在表达式的右边,例如:datum = yield,可以产出值,也可以不产出--如果yield关键字后面没有表达式,那么生成器产出None.
  • 协程可能从调用方接受数据,调用方是通过send(datum)的方式把数据提供给协程使用,而不是next(...)函数,通常调用方会把值推送给协程。
  • 协程可以把控制器让给中心调度程序,从而激活其他的协程

所以总体上在协程中把yield看做是控制流程的方式。

在前一篇《一文彻底搞懂Python可迭代(Iterable)、迭代器(Iterator)和生成器(Generator)的概念》 的文中,知道生成器(Generator)可由以下两种方式定义:

  • 列表生成器
  • 使用yield定义的函数

在Python早期的版本中协程也是通过生成器来实现的,也就是基于生成器的协程(Generator-based Coroutines)。在前一篇介绍生成器的文章末尾举了一个生产者-消费者的例子,就是基于生成器的协程来实现的。

def producer(c):
 n = 0
 while n < 5:
 n += 1
 print('producer {}'.format(n))
 r = c.send(n)
 print('consumer return {}'.format(r))


def consumer():
 r = ''
 while True:
 n = yield r
 if not n:
 return
 print('consumer {} '.format(n))
 r = 'ok'


if __name__ == '__main__':
 c = consumer()
 next(c) # 启动consumer
 producer(c)

看了这段代码,相信很多初学者和我一样对基于生成器的协程实现其实很难马上就能够根据业务写出自己的协程代码。Python实现者们也注意到这个问题,因为它太不Pythonic了。而基于生成器的协程也将被废弃,因此本文将重点介绍asyncio包的使用,以及涉及到的一些相关类概念。

注:我使用的Python环境是3.7。

0x00 何为协程(Coroutine)

协程(Coroutine)是在线程中执行的,可理解为微线程,但协程的切换没有上下文的消耗,它比线程更加轻量些。一个协程可以随时中断自己让另一个协程开始执行,也可以从中断处恢复并继续执行,它们之间的调度是由程序员来控制的(可以看本文开篇处生产者-消费者的代码)。

定义一个协程

在Python3.5+版本新增了aysnc和await关键字,这两个语法糖让我们非常方便地定义和使用协程。

在函数定义时用async声明就定义了一个协程。

import asyncio

# 定义了一个简单的协程
async def simple_async():
 print('hello')
 await asyncio.sleep(1) # 休眠1秒
 print('python')
 
# 使用asynio中run方法运行一个协程
asyncio.run(simple_async())

# 执行结果为
# hello
# python

在协程中如果要调用另一个协程就使用await。要注意await关键字要在async定义的函数中使用,而反过来async函数可以不出现await

# 定义了一个简单的协程
async def simple_async():
 print('hello')
 
asyncio.run(simple_async())

# 执行结果
# hello

asyncio.run()将运行传入的协程,负责管理asyncio事件循环。

除了run()方法可直接执行协程外,还可以使用事件循环loop

async def do_something(index):
 print(f'start {time.strftime("%X")}', index)
 await asyncio.sleep(1)
 print(f'finished at {time.strftime("%X")}', index)


def test_do_something():
 # 生成器产生多个协程对象
 task = [do_something(i) for i in range(5)]

 # 获取一个事件循环对象
 loop = asyncio.get_event_loop()
 # 在事件循环中执行task列表
 loop.run_until_complete(asyncio.wait(task))
 loop.close()

test_do_something()

# 运行结果
# start 00:04:03 3
# start 00:04:03 4
# start 00:04:03 1
# start 00:04:03 2
# start 00:04:03 0
# finished at 00:04:04 3
# finished at 00:04:04 4
# finished at 00:04:04 1
# finished at 00:04:04 2
# finished at 00:04:04 0

可以看出几乎同时启动了所有的协程。

其实翻阅源码可知asyncio.run()的实现也是封装了loop对象及其调用。而asyncio.run()每次都会创建一个新的事件循环对象用于执行协程。

0x01 Awaitable对象

在Python中可等待(Awaitable)对象有:协程(corountine)、任务(Task)、Future。即这些对象可以使用await关键字进行调用

await awaitable_object

1. 协程(Coroutine)

协程由async def声明定义,一个协程可由另一个协程使用await进行调用

async def nested():
 print('in nested func')
 return 13


async def outer():

 # 要使用await 关键字 才会执行一个协程函数返回的协程对象
 print(await nested())

asyncio.run(outer())

# 执行结果
# in nested func
# 13

如果在outer()方法中直接调用nested()而不使用await,将抛出一个RuntimeWarning

async def outer():
 # 直接调用协程函数不会发生执行,只是返回一个 coroutine 对象
 nested()
 
asyncio.run(outer())

运行程序,控制台将输出以下信息

RuntimeWarning: coroutine 'nested' was never awaited
  nested()
RuntimeWarning: Enable tracemalloc to get the object allocation traceback

2. 任务(Task)

任务(Task)是可以用来并发地执行协程。可以使用asyncio.create_task()将一个协程对象封装成任务,该任务将很快被排入调度队列并执行。

async def nested():
 print('in nested func')
 return 13

async def create_task():
 # create_task 将一个协程对象打包成一个 任务时,该协程就会被自动调度运行
 task = asyncio.create_task(nested())
 # 如果要看到task的执行结果
 # 可以使用await等待协程执行完成,并返回结果
 ret = await task
 print(f'nested return {ret}')

asyncio.run(create_task())

# 运行结果
# in nested func
# nested return 13

注:关于并发下文还会详细说明。

3. Future

Future是一种特殊的低层级(low-level)对象,它是异步操作的最终结果(eventual result)。
当一个 Future 对象 被等待,这意味着协程将保持等待直到该 Future 对象在其他地方操作完毕。

通常在应用层代码不会直接创建Future对象。在某些库和asyncio模块中的会使用到该对象。

async def used_future_func():
 await function_that_returns_a_future_object()

0x02 并发

1. Task

前面我们知道Task可以并发地执行。  asyncio.create_task()就是一个把协程封装成Task的方法。

async def do_after(what, delay):
 await asyncio.sleep(delay)
 print(what)

# 利用asyncio.create_task创建并行任务
async def corun():
 task1 = asyncio.create_task(do_after('hello', 1)) # 模拟执行1秒的任务
 task2 = asyncio.create_task(do_after('python', 2)) # 模拟执行2秒的任务

 print(f'started at {time.strftime("%X")}')
 # 等待两个任务都完成,两个任务是并行的,所以总时间两个任务中最大的执行时间
 await task1
 await task2

 print(f'finished at {time.strftime("%X")}')

asyncio.run(corun())

# 运行结果
# started at 23:41:08
# hello
# python
# finished at 23:41:10

task1是一个执行1秒的任务,task2是一个执行2秒的任务,两个任务并发的执行,总共消耗2秒。

2. gather

除了使用asyncio.create_task()外还可以使用asyncio.gather(),这个方法接收协程参数列表

async def do_after(what, delay):
 await asyncio.sleep(delay)
 print(what)
 
async def gather():
 print(f'started at {time.strftime("%X")}')
 # 使用gather可将多个协程传入
 await asyncio.gather(
 do_after('hello', 1),
 do_after('python', 2),
 )
 print(f'finished at {time.strftime("%X")}')

asyncio.run(gather())

# 运行结果
# started at 23:47:50
# hello
# python
# finished at 23:47:52

两个任务消耗的时间为其中消耗时间最长的任务。

0x03 引用

docs.python.org/3/library/a…

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对三水点靠木的支持。

Python 相关文章推荐
用Python程序抓取网页的HTML信息的一个小实例
May 02 Python
Python中selenium实现文件上传所有方法整理总结
Apr 01 Python
Python下载网络文本数据到本地内存的四种实现方法示例
Feb 05 Python
Python机器学习k-近邻算法(K Nearest Neighbor)实例详解
Jun 25 Python
Face++ API实现手势识别系统设计
Nov 21 Python
Python常见读写文件操作实例总结【文本、json、csv、pdf等】
Apr 15 Python
Python GUI库PyQt5图形和特效样式QSS介绍
Feb 25 Python
Python对称的二叉树多种思路实现方法
Feb 28 Python
Python编程快速上手——Excel表格创建乘法表案例分析
Feb 28 Python
python批量替换文件名中的共同字符实例
Mar 05 Python
django ListView的使用 ListView中获取url中的参数值方式
Mar 27 Python
Python数据分析之pandas函数详解
Apr 21 Python
Python玩转加密的技巧【推荐】
May 13 #Python
11个Python3字典内置方法大全与示例汇总
May 13 #Python
python中的数据结构比较
May 13 #Python
Python中函数的基本定义与调用及内置函数详解
May 13 #Python
python实现弹跳小球
May 13 #Python
Python开发之Nginx+uWSGI+virtualenv多项目部署教程
May 13 #Python
PyQt5的PyQtGraph实践系列3之实时数据更新绘制图形
May 13 #Python
You might like
用libTemplate实现静态网页的生成
2006/10/09 PHP
优化PHP代码技巧的小结
2013/06/02 PHP
PHP生成树的方法
2015/07/28 PHP
php构造方法中析构方法在继承中的表现
2016/04/12 PHP
PHP连续签到功能实现方法详解
2019/12/04 PHP
[原创]用javascript实现检测指定目录是否存在的方法
2008/01/12 Javascript
isArray()函数(JavaScript中对象类型判断的几种方法)
2009/11/26 Javascript
jquery json 实例代码
2010/12/02 Javascript
ModelDialog JavaScript模态对话框类代码
2011/04/17 Javascript
jQuery之尺寸调整组件的深入解析
2013/06/19 Javascript
jQuery UI插件自定义confirm确认框的方法
2015/03/20 Javascript
js如何改变文章的字体大小
2016/01/08 Javascript
微信小程序slider组件使用详解
2018/01/31 Javascript
node puppeteer(headless chrome)实现网站登录
2018/05/09 Javascript
jQuery实现数字自动增加或者减少的动画效果示例
2018/12/11 jQuery
node app 打包工具pkg的具体使用
2019/01/17 Javascript
JS中实现浅拷贝和深拷贝的代码详解
2019/06/05 Javascript
jQuery实现聊天对话框
2020/02/08 jQuery
微信小程序对图片进行canvas压缩的方法示例详解
2020/11/12 Javascript
[01:33:07]VGJ.T vs Newbee Supermajor 败者组 BO3 第一场 6.6
2018/06/07 DOTA
Python set集合类型操作总结
2014/11/07 Python
python fabric使用笔记
2015/05/09 Python
python 爬取微信文章
2016/01/30 Python
浅谈使用Python内置函数getattr实现分发模式
2018/01/22 Python
解决pandas中读取中文名称的csv文件报错的问题
2018/07/04 Python
python logging重复记录日志问题的解决方法
2018/07/12 Python
Python使用QQ邮箱发送邮件报错smtplib.SMTPAuthenticationError
2019/12/20 Python
Python tkinter三种布局实例详解
2020/01/06 Python
浅谈keras使用中val_acc和acc值不同步的思考
2020/06/18 Python
PyTorch中的拷贝与就地操作详解
2020/12/09 Python
二年级体育教学反思
2014/01/15 职场文书
财产保全担保书范文
2014/04/01 职场文书
《称象》教学反思
2014/04/25 职场文书
井冈山红色之旅感想
2014/10/07 职场文书
建筑工地文明标语
2014/10/09 职场文书
springboot中的pom文件 project报错问题
2022/01/18 Java/Android