侯体宗的博客
  • 首页
  • Hyperf版
  • beego仿版
  • 人生(杂谈)
  • 技术
  • 关于我
  • 更多分类
    • 文件下载
    • 文字修仙
    • 中国象棋ai
    • 群聊
    • 九宫格抽奖
    • 拼图
    • 消消乐
    • 相册

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

Python  /  管理员 发布于 7年前   414

前言

从语法上来看,协程和生成器类似,都是定义体中包含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 13async 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 13async 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中可迭代(Iterable)、迭代器(Iterator)与生成器(Generator)的概念
    下一条:
    11个Python3字典内置方法大全与示例汇总
  • 昵称:

    邮箱:

    0条评论 (评论内容有缓存机制,请悉知!)
    最新最热
    • 分类目录
    • 人生(杂谈)
    • 技术
    • linux
    • Java
    • php
    • 框架(架构)
    • 前端
    • ThinkPHP
    • 数据库
    • 微信(小程序)
    • Laravel
    • Redis
    • Docker
    • Go
    • swoole
    • Windows
    • Python
    • 苹果(mac/ios)
    • 相关文章
    • 在python语言中Flask框架的学习及简单功能示例(0个评论)
    • 在Python语言中实现GUI全屏倒计时代码示例(0个评论)
    • Python + zipfile库实现zip文件解压自动化脚本示例(0个评论)
    • python爬虫BeautifulSoup快速抓取网站图片(1个评论)
    • vscode 配置 python3开发环境的方法(0个评论)
    • 近期文章
    • 在windows10中升级go版本至1.24后LiteIDE的Ctrl+左击无法跳转问题解决方案(0个评论)
    • 智能合约Solidity学习CryptoZombie第四课:僵尸作战系统(0个评论)
    • 智能合约Solidity学习CryptoZombie第三课:组建僵尸军队(高级Solidity理论)(0个评论)
    • 智能合约Solidity学习CryptoZombie第二课:让你的僵尸猎食(0个评论)
    • 智能合约Solidity学习CryptoZombie第一课:生成一只你的僵尸(0个评论)
    • 在go中实现一个常用的先进先出的缓存淘汰算法示例代码(0个评论)
    • 在go+gin中使用"github.com/skip2/go-qrcode"实现url转二维码功能(0个评论)
    • 在go语言中使用api.geonames.org接口实现根据国际邮政编码获取地址信息功能(1个评论)
    • 在go语言中使用github.com/signintech/gopdf实现生成pdf分页文件功能(0个评论)
    • gmail发邮件报错:534 5.7.9 Application-specific password required...解决方案(0个评论)
    • 近期评论
    • 122 在

      学历:一种延缓就业设计,生活需求下的权衡之选中评论 工作几年后,报名考研了,到现在还没认真学习备考,迷茫中。作为一名北漂互联网打工人..
    • 123 在

      Clash for Windows作者删库跑路了,github已404中评论 按理说只要你在国内,所有的流量进出都在监控范围内,不管你怎么隐藏也没用,想搞你分..
    • 原梓番博客 在

      在Laravel框架中使用模型Model分表最简单的方法中评论 好久好久都没看友情链接申请了,今天刚看,已经添加。..
    • 博主 在

      佛跳墙vpn软件不会用?上不了网?佛跳墙vpn常见问题以及解决办法中评论 @1111老铁这个不行了,可以看看近期评论的其他文章..
    • 1111 在

      佛跳墙vpn软件不会用?上不了网?佛跳墙vpn常见问题以及解决办法中评论 网站不能打开,博主百忙中能否发个APP下载链接,佛跳墙或极光..
    • 2016-10
    • 2016-11
    • 2018-04
    • 2020-03
    • 2020-04
    • 2020-05
    • 2020-06
    • 2022-01
    • 2023-07
    • 2023-10
    Top

    Copyright·© 2019 侯体宗版权所有· 粤ICP备20027696号 PHP交流群

    侯体宗的博客