为何你还不懂得如何使用Python协程
关于我
一个有思想的程序猿,终身学习实践者,目前在一个创业团队任team lead,技术栈涉及Android、Python、Java和Go,这个也是我们团队的主要技术栈。
Github:https://github.com/hylinux1024
微信公众号:终身开发者(angrycode)
在前一篇《一文彻底搞懂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
)是在线程中执行的,可理解为微线程,但协程的切换没有上下文的消耗,它比线程更加轻量些。一个协程可以随时中断自己让另一个协程开始执行,也可以从中断处恢复并继续执行,它们之间的调度是由程序员来控制的(可以看本文开篇处生产者-消费者的代码)。
定义一个协程
在Python
3.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 引用
为何你还不懂得如何使用Python协程的更多相关文章
- day-5 python协程与I/O编程深入浅出
基于python编程语言环境,重新学习了一遍操作系统IO编程基本知识,同时也学习了什么是协程,通过实际编程,了解进程+协程的优势. 一.python协程编程实现 1. 什么是协程(以下内容来自维基百 ...
- 从yield 到yield from再到python协程
yield 关键字 def fib(): a, b = 0, 1 while 1: yield b a, b = b, a+b yield 是在:PEP 255 -- Simple Generator ...
- 关于python协程中aiorwlock 使用问题
最近工作中多个项目都开始用asyncio aiohttp aiomysql aioredis ,其实也是更好的用python的协程,但是使用的过程中也是遇到了很多问题,最近遇到的就是 关于aiorwl ...
- [转载] Python协程从零开始到放弃
Python协程从零开始到放弃 Web安全 作者:美丽联合安全MLSRC 2017-10-09 3,973 Author: lightless@Meili-inc Date: 2017100 ...
- 00.用 yield 实现 Python 协程
来源:Python与数据分析 链接: https://mp.weixin.qq.com/s/GrU6C-x4K0WBNPYNJBCrMw 什么是协程 引用官方的说法: 协程是一种用户态的轻量级线程,协 ...
- python协程详解
目录 python协程详解 一.什么是协程 二.了解协程的过程 1.yield工作原理 2.预激协程的装饰器 3.终止协程和异常处理 4.让协程返回值 5.yield from的使用 6.yield ...
- Python协程与Go协程的区别二
写在前面 世界是复杂的,每一种思想都是为了解决某些现实问题而简化成的模型,想解决就得先面对,面对就需要选择角度,角度决定了模型的质量, 喜欢此UP主汤质看本质的哲学科普,其中简洁又不失细节的介绍了人类 ...
- python协程详解,gevent asyncio
python协程详解,gevent asyncio 新建模板小书匠 #协程的概念 #模块操作协程 # gevent 扩展模块 # asyncio 内置模块 # 基础的语法 1.生成器实现切换 [1] ...
- Python核心技术与实战——十六|Python协程
我们在上一章将生成器的时候最后写了,在Python2中生成器还扮演了一个重要的角色——实现Python的协程.那什么是协程呢? 协程 协程是实现并发编程的一种方式.提到并发,肯很多人都会想到多线程/多 ...
随机推荐
- 数据库系统概念:SQL的数据类型与模式、授权
public class DataBase { public static void main() { } } /* 4.5 SQL的数据类型与模式 4.5.1 SQL的日期与时间类型 SQL标准支持 ...
- 简单的量子算法(一):Hadamard 变换、Parity Problem
Hadamard Transform Hadamard 变换在量子逻辑门中提过,只不过那时是单量子的Hadamard门,负责把\(|1\rangle\)变成\(|-\rangle\),\(|0\ran ...
- 洛谷P2055 [ZJOI2009]假期的宿舍 题解
题目链接: https://www.luogu.org/problemnew/show/P2055 分析: 这道题比较简单,二分图的练习题(当然最大流同理). 易得我们可以将人放在一侧,床放在一侧. ...
- HashSet源码分析:JDK源码系列
1.简介 继续分析源码,上一篇文章把HashMap的分析完毕.本文开始分析HashSet简单的介绍一下. HashSet是一个无重复元素集合,内部使用HashMap实现,所以HashMap的特征耶继承 ...
- 个人永久性免费-Excel催化剂功能第18波-在Excel上也能玩上词云图
这年头数据可视化日新月异,在Excel上做数据分析,最后一步,难免要搞个图表输出高大上一回,微软也深知此道,在Excel2016上更新了一大波图表功能,市场上很耀眼的词云图还是没加进来,虽然在各大的在 ...
- 安卓BindService笔记
1 前言 最近学习到了安卓的service,记录一下自己对BindService的理解,学习教程以及部分代码来自菜鸟教程的android教程:菜鸟教程安卓端BindService链接 2 正文 先贴一 ...
- React中创建组件的3种方式
目前作者所知道的创建react组件的方式有三种: 函数式定义(无状态组件) function MyComponent(props){ return( <h1>mycomponent< ...
- JavaSE总结(一)
一.Java 简介 Java是由Sun Microsystems公司于1995年5月推出的Java面向对象程序设计语言和Java平台的总称.由James Gosling和同事们共同研发,并在1995年 ...
- java并发笔记之证明 synchronized锁 是否真实存在
警告⚠️:本文耗时很长,先做好心理准备 证明:偏向锁.轻量级锁.重量级锁真实存在 由[java并发笔记之java线程模型]链接: https://www.cnblogs.com/yuhangwang/ ...
- dz6.0的一个sql注入漏洞
今天开始着手分析第一个漏洞,找了一上午靶机,发现一个含有成人内容的违法网站是用dz6.0搭的,今天就看看dz这个版本的洞了 问题函数位置:my.php第623行 if(is_array($descri ...