python协程详解(为什么你还不懂得怎么使用Python协程)
python协程详解
为什么你还不懂得怎么使用Python协程前言
从语法上来看,协程和生成器类似,都是定义体中包含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…
总结
以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对开心学习网的支持。
原文链接:https://juejin.im/post/5ccf0d18e51d453b557dc340
- python计算csv的行数(对Python 多线程统计所有csv文件的行数方法详解)
- python3有哪些内置模块(Python3.5内置模块之os模块、sys模块、shutil模块用法实例分析)
- python人脸识别库有几个(Python人脸识别第三方库face_recognition接口说明文档)
- python操作pandas(详解Python学习之安装pandas)
- vscode如何配置python环境(VSCode Python开发环境配置的详细步骤)
- python设置按钮(Python按钮的响应事件详解)
- python如何将运行结果存入txt中(详解python读取和输出到txt)
- python人脸识别实战视频(Python学习笔记之图片人脸检测识别实例教程)
- python实现七个基本算法(python实现维吉尼亚算法)
- python中字符串截取规则(Python中的字符串切片截取字符串的详解)
- python制作彩色字符(Python3利用print输出带颜色的彩色字体示例代码)
- 用python查看运行进程(在Python运行时动态查看进程内部信息的方法)
- python浪漫表白源码(python七夕浪漫表白源码)
- python创建进程的方法(Python多进程fork函数详解)
- python中对象方法和顶级方法(Python3.5面向对象程序设计之类的继承和多态详解)
- python 正则表达式语法大全(python re库的正则表达式入门学习教程)
- 八月再见 愿你岁月不扰,余生静好(八月再见愿你岁月不扰)
- 赏读 八月再见,九月你好(赏读八月再见九月你好)
- 散文 八月再见,九月,我在风中等你(散文八月再见九月)
- 8月再见 9月你好(8月再见)
- 魔兽世界 设计师爆料,原始版本并无PVP,跨阵营属于返璞归真(魔兽世界设计师爆料)
- 吐槽完《弧光大作战》之后,我们和设计师聊了聊魔兽首款手游的立项初衷和未来(吐槽完弧光大作战之后)
热门推荐
- thinkphp静态怎么设置(浅谈thinkphp的nginx配置,以及重写隐藏index.php入口文件方法)
- python进程管理教程(Python I/O与进程的详细讲解)
- js如何操作json字符串
- mvc中使用uploadify批量上传
- html转ppt(HTML里显示pdf、word、xls、ppt的方法示例)
- 用js做一个计算器(使用JS实现简易计算器)
- mongodb连接池
- docker的常用的命令(Docker 清理命令集锦)
- dedecms网站空白(DEDECMS支持中文水印的解决方法)
- pandas数据分组使用方法(在Pandas中DataFrame数据合并,连接concat,merge,join的实例)
排行榜
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9