为何你还不懂得如何使用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)是在线程中执行的,可理解为微线程,但协程的切换没有上下文的消耗,它比线程更加轻量些。一个协程可以随时中断自己让另一个协程开始执行,也可以从中断处恢复并继续执行,它们之间的调度是由程序员来控制的(可以看本文开篇处生产者-消费者的代码)。
定义一个协程
在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 引用
为何你还不懂得如何使用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的协程.那什么是协程呢? 协程 协程是实现并发编程的一种方式.提到并发,肯很多人都会想到多线程/多 ...
随机推荐
- Balking设计模式
import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.util.ArrayLi ...
- Excel催化剂开源第15波-VSTO开发之DataTable数据导出至单元格区域
上篇提到如何从Excel界面上拿到用户的数据,另外反方向的怎样输出给用户数据,也是关键之处. VSTO最大的优势是,这双向的过程中,全程有用户的交互操作. 而一般IT型的程序,都是脱离用户的操作,只能 ...
- 2-1. 基于OpenSSL的传输子系统实现
一. 基本传输子系统程序设计 客户端可上传文件至服务器,或下载服务器上的文件 系统程序构架: 客户端 服务器 TCP建立连接 menu()-> 上传命令.下载命令 close(socket) T ...
- vijos p1484 ISBN号码
#include<iostream>#include<string>#include<cctype>using namespace std;int main() { ...
- Linux系统安装Tomcat——.tar.gz版(old)
这里简单地阐述一下rpm.deb.tar.gz的区别. rpm格式的软件包适用于基于Red Hat发行版的系统,如Red Hat Linux.SUSE.Fedora. deb格式的软件包则是适用于基于 ...
- storm入门demo
一.storm入门demo的介绍 storm的入门helloworld有2种方式,一种是本地的,另一种是远程. 本地实现: 本地写好demo之后,不用搭建storm集群,下载storm的相关jar包即 ...
- React躬行记(11)——Redux基础
Redux是一个可预测的状态容器,不但融合了函数式编程思想,还严格遵循了单向数据流的理念.Redux继承了Flux的架构思想,并在此基础上进行了精简.优化和扩展,力求用最少的API完成最主要的功能,它 ...
- ioc和aop的区别
IoC,(Inverse of Control)控制反转,其包含两个内容:其一是控制,其二是反转.在程序中,被调用类的选择控制权从调用它的类中移除,转交给第三方裁决.这个第三方指的就是Spring的容 ...
- 【Android】SDK Manager 设置代理
这里是 Mac 系统下,Windows 环境类似.打开 Android SDK Manager, Proxy Settings 设置如下所示: PS: 注意勾选 "Force https:/ ...
- vue-cli3.0创建项目报npm install --loglevel error 踩坑的那把辛酸泪
创建项目 vue create vue-pro 然后如下图 一开始以为是npm的问题,卸载了Mac的node ,安装nvm,然后再安装node (可参考: Mac中nvm的安装和使用 https: ...