Python——协程(Coroutine),异步IO

news2024/11/19 1:31:08

目录

生成器(Generator)

yield表达式的使用

生产者和消费者模型

​编辑

 yield from表达式

协程(Coroutine)

@asyncio.coroutine

async/await

总结


由于GIL的存在,导致Python多线程性能甚至比单线程更糟。

 于是出现了协程(Coroutine)这么个东西。

协程由于由程序主动控制切换,没有线程切换的开销,所以执行效率极高。对于IO密集型任务非常适用,如果是cpu密集型,推荐多进程+协程的方式。

在Python3.4之前,官方没有对协程的支持,存在一些三方库的实现,比如gevent和Tornado。3.4之后就内置了asyncio标准库,官方真正实现了协程这一特性。

而Python对协程的支持,是通过Generator实现的,协程是遵循某些规则的生成器。因此,我们在了解协程之前,我们先要学习生成器。

生成器(Generator)

我们这里主要讨论yieldyield from这两个表达式,这两个表达式和协程的实现息息相关。

  • Python2.5中引入yield表达式,参见PEP342
  • Python3.3中增加yield from语法,参见PEP380,

方法中包含yield表达式后,Python会将其视作generator对象,不再是普通的方法。

yield表达式的使用

我们先来看该表达式的具体使用:

def test():
    print("generator start")
    n = 1
    while True:
        yield_expression_value = yield n
        print("yield_expression_value = %d" % yield_expression_value)
        n += 1


# ①创建generator对象
generator = test()
print(type(generator))

print("\n---------------\n")

# ②启动generator
next_result = generator.__next__()
print("next_result = %d" % next_result)

print("\n---------------\n")

# ③发送值给yield表达式
send_result = generator.send(666)
print("send_result = %d" % send_result)

执行结果:

<class 'generator'>

---------------

generator start
next_result = 1

---------------

yield_expression_value = 666
send_result = 2

方法说明:

  • __next__()方法: 作用是启动或者恢复generator的执行,相当于send(None)

  • send(value)方法:作用是发送值给yield表达式。启动generator则是调用send(None)

执行结果的说明:

  • ①创建generator对象:包含yield表达式的函数将不再是一个函数,调用之后将会返回generator对象

  • ②启动generator:使用生成器之前需要先调用__next__或者send(None),否则将报错。启动generator后,代码将执行到yield出现的位置,也就是执行到yield n,然后将n传递到generator.__next__()这行的返回值。(注意,生成器执行到yield n后将暂停在这里,直到下一次生成器被启动)

  • ③发送值给yield表达式:调用send方法可以发送值给yield表达式,同时恢复生成器的执行。生成器从上次中断的位置继续向下执行,然后遇到下一个yield,生成器再次暂停,切换到主函数打印出send_result。

理解这个demo的关键是:生成器启动或恢复执行一次,将会在yield处暂停。上面的第②步仅仅执行到了yield n,并没有执行到赋值语句,到了第③步,生成器恢复执行才给yield_expression_value赋值。

生产者和消费者模型

上面的例子中,代码中断-->切换执行,体现出了协程的部分特点。

我们再举一个生产者、消费者的例子

def consumer():
    print("[CONSUMER] start")
    r = 'start'
    while True:
        n = yield r
        if not n:
            print("n is empty")
            continue
        print("[CONSUMER] Consumer is consuming %s" % n)
        r = "200 ok"


def producer(c):
    # 启动generator
    start_value = c.send(None)
    print(start_value)
    n = 0
    while n < 3:
        n += 1
        print("[PRODUCER] Producer is producing %d" % n)
        r = c.send(n)
        print('[PRODUCER] Consumer return: %s' % r)
    # 关闭generator
    c.close()


# 创建生成器
c = consumer()
# 传入generator
producer(c)

 执行结果:

[CONSUMER] start
start
[PRODUCER] producer is producing 1
[CONSUMER] consumer is consuming 1
[PRODUCER] Consumer return: 200 ok
[PRODUCER] producer is producing 2
[CONSUMER] consumer is consuming 2
[PRODUCER] Consumer return: 200 ok
[PRODUCER] producer is producing 3
[CONSUMER] consumer is consuming 3
[PRODUCER] Consumer return: 200 ok

  

 

 

 yield from表达式

Python3.3版本新增yield from语法,新语法用于将一个生成器部分操作委托给另一个生成器。此外,允许子生成器(即yield from后的“参数”)返回一个值,该值可供委派生成器(即包含yield from的生成器)使用。并且在委派生成器中,可对子生成器进行优化。

我们先来看最简单的应用,例如:

# 子生成器
def test(n):
    i = 0
    while i < n:
        yield i
        i += 1

# 委派生成器
def test_yield_from(n):
    print("test_yield_from start")
    yield from test(n)
    print("test_yield_from end")


for i in test_yield_from(3):
    print(i)

输出:

test_yield_from start
0
1
2
test_yield_from end

这里我们仅仅给这个生成器添加了一些打印,如果是正式的代码中,你可以添加正常的执行逻辑。

如果上面的test_yield_from函数中有两个yield from语句,将串行执行。比如将上面的test_yield_from函数改写成这样:

def test_yield_from(n):
    print("test_yield_from start")
    yield from test(n)
    print("test_yield_from doing")
    yield from test(n)
    print("test_yield_from end")

将输出:

test_yield_from start
0
1
2
test_yield_from doing
0
1
2
test_yield_from end

在这里,yield from起到的作用相当于下面写法的简写形式

for item in test(n):
    yield item

看起来这个yield from也没做什么大不了的事,其实它还帮我们处理了异常之类的。具体可以看stackoverflow上的这个问题:In practice, what are the main uses for the new “yield from” syntax in Python 3.3?

协程(Coroutine)

  • Python3.4开始,新增了asyncio相关的API,语法使用@asyncio.coroutineyield from实现协程
  • Python3.5中引入async/await语法,参见PEP492

我们先来看Python3.4的实现。

@asyncio.coroutine

Python3.4中,使用@asyncio.coroutine装饰的函数称为协程。不过没有从语法层面进行严格约束。

对于Python原生支持的协程来说,Python对协程和生成器做了一些区分,便于消除这两个不同但相关的概念的歧义:

  • 标记了@asyncio.coroutine装饰器的函数称为协程函数,iscoroutinefunction()方法返回True
  • 调用协程函数返回的对象称为协程对象,iscoroutine()函数返回True

举个栗子,我们给上面yield from的demo中添加@asyncio.coroutine

import asyncio

...

@asyncio.coroutine
def test_yield_from(n):
    ...

# 是否是协程函数
print(asyncio.iscoroutinefunction(test_yield_from))
# 是否是协程对象
print(asyncio.iscoroutine(test_yield_from(3)))

毫无疑问输出结果是True。

可以看下@asyncio.coroutine的源码中查看其做了什么,我将其源码简化下,大致如下:

import functools
import types
import inspect

def coroutine(func):
    # 判断是否是生成器
    if inspect.isgeneratorfunction(func):
        coro = func
    else:
        # 将普通函数变成generator
        @functools.wraps(func)
        def coro(*args, **kw):
            res = func(*args, **kw)
            res = yield from res
            return res
    # 将generator转换成coroutine
    wrapper = types.coroutine(coro)
    # For iscoroutinefunction().
    wrapper._is_coroutine = True
    return wrapper

将这个装饰器标记在一个生成器上,就会将其转换成coroutine。

然后,我们来实际使用下@asyncio.coroutineyield from

import asyncio

@asyncio.coroutine
def compute(x, y):
    print("Compute %s + %s ..." % (x, y))
    yield from asyncio.sleep(1.0)
    return x + y

@asyncio.coroutine
def print_sum(x, y):
    result = yield from compute(x, y)
    print("%s + %s = %s" % (x, y, result))

loop = asyncio.get_event_loop()
print("start")
# 中断调用,直到协程执行结束
loop.run_until_complete(print_sum(1, 2))
print("end")
loop.close()

执行结果:

start
Compute 1 + 2 ...
1 + 2 = 3
end

print_sum这个协程中调用了子协程compute,它将等待compute执行结束才返回结果。

这个demo点调用流程如下图:

EventLoop将会把print_sum封装成Task对象

流程图展示了这个demo的控制流程,不过没有展示其全部细节。比如其中“暂停”的1s,实际上创建了一个future对象, 然后通过BaseEventLoop.call_later()在1s后唤醒这个任务。

值得注意的是,@asyncio.coroutine将在Python3.10版本中移除。

async/await

Python3.5开始引入async/await语法(PEP 492),用来简化协程的使用并且便于理解。

async/await实际上只是@asyncio.coroutineyield from的语法糖:

  • @asyncio.coroutine替换为async
  • yield from替换为await

即可。

比如上面的例子:

import asyncio


async def compute(x, y):
    print("Compute %s + %s ..." % (x, y))
    await asyncio.sleep(1.0)
    return x + y


async def print_sum(x, y):
    result = await compute(x, y)
    print("%s + %s = %s" % (x, y, result))


loop = asyncio.get_event_loop()
print("start")
loop.run_until_complete(print_sum(1, 2))
print("end")
loop.close()

我们再来看一个asyncio中Future的例子:

import asyncio

future = asyncio.Future()


async def coro1():
    print("wait 1 second")
    await asyncio.sleep(1)
    print("set_result")
    future.set_result('data')


async def coro2():
    result = await future
    print(result)


loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait([
    coro1()
    coro2()
]))
loop.close()

输出结果:

wait 1 second
(大约等待1秒)
set_result
data

这里await后面跟随的future对象,协程中yield from或者await后面可以调用future对象,其作用是:暂停协程,直到future执行结束或者返回result或抛出异常。

而在我们的例子中,await future必须要等待future.set_result('data')后才能够结束。将coro2()作为第二个协程可能体现得不够明显,可以将协程的调用改成这样:

loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait([
    # coro1(),
    coro2(),
    coro1()
]))
loop.close()

输出的结果仍旧与上面相同。

其实,async这个关键字的用法不止能用在函数上,还有async with异步上下文管理器,async for异步迭代器. 对这些感兴趣且觉得有用的可以网上找找资料,这里限于篇幅就不过多展开了。

总结

本文就生成器和协程做了一些学习、探究和总结,不过并没有做过多深入的研究。权且作为入门到一个笔记,之后将会尝试自己实现一下异步API,希望有助于理解学习。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/62959.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Arduino UNO通过PCF8574串行IIC接口驱动LCD1602/LCD2004液晶屏

LCD1602/2004液晶屏简介 LCD1602液晶显示器是广泛使用的一种字符型液晶显示模块。可以显示2行每行16个字符&#xff0c;总共32个字符。字符型液晶显示模块是一种专门用于显示字母、数字和符号等的点阵式LCD&#xff0c;常用161&#xff0c;162&#xff0c;202和402等的模块。不…

Go编程项目实战教程

Go编程项目实战教程 在这个面向初学者的 Go 编程语言课程中&#xff0c;您将通过构建 11 个项目来提高您的 Go 编程技能 课程英文名&#xff1a;Learn Go Programming by Building 11 Projects – Full Course 此视频教程共21.0小时&#xff0c;中英双语字幕&#xff0c;画质…

数据之道读书笔记-07打造“数字孪生”的数据全量感知能力

数据之道读书笔记-07打造“数字孪生”的数据全量感知能力 在信息化时代构建的IT系统&#xff0c;基本上是功能化、烟囱化、封闭式的&#xff0c;只能给企业内部经过培训的专业人员使用&#xff0c;所有的决策数据和我们信任的IT系统基本都是靠人来录入数据。但是&#xff0c;人…

Android移动开发基础——实训项目:个人财务软件

目录 步骤 1. 项目计划 需求分析 程序流程图 2. 实现功能模块 2.1 登录模块 前提 软件&#xff1a;Android Studio开发工具、JDK1.8以上版本 目标&#xff1a;编写个人财务软件 步骤 &#xff08;1&#xff09;根据设计题目要求的指标&#xff0c;通过查阅有关资料…

某学生宿舍楼设计

目 录 1.建筑设计部分 1 1.1工程概况 1 1.2设计依据 2 1.3标高及建筑细部作法 2 1.4平面设计 2 1.4.1建筑方案设计 2 1.4.2建筑做法 4 1.4.3 建筑设计成果 6 2.结构设计部分 7 2.1结构平面设计 7 2.2构件截面尺寸的初步确定 8 2.3荷载统计 10 2.3.1楼屋面及卫生间恒活计算 10 2…

热加载技术:修改Python代码并实时查看结果 ⛵

&#x1f4a1; 作者&#xff1a;韩信子ShowMeAI &#x1f4d8; Python3◉技能提升系列&#xff1a;https://www.showmeai.tech/tutorials/56 &#x1f4d8; 本文地址&#xff1a;https://www.showmeai.tech/article-detail/406 &#x1f4e2; 声明&#xff1a;版权所有&#xf…

基于MindSpore框架的道路场景语义分割方法研究

概述 本文以华为最新国产深度学习框架Mindspore为基础&#xff0c;将城市道路下的实况图片解析作为任务背景&#xff0c;以复杂城市道路进行高精度的语义分割为任务目标&#xff0c;对上述难处进行探究并提出相应方案&#xff0c;成功地在Cityscapes数据集上完成了语义分割任务…

vue3+ts做echarts做一个简单的折线渐变图

vue3做echarts做一个简单的折线渐变图 效果 代码&#xff1a; </template> <div><div class"date-change"><el-date-picker size"small" v-model"dateValue" value-format"YYYY-MM-DD" type"daterange&qu…

Leetcode 72. 编辑距离

最近在写dp问题的时候&#xff0c;写到这个经典题&#xff0c;对于里面三个转换方程没太懂&#xff0c;偶然在评论区找到一个非常非常清楚的解释&#xff0c;顺便就把这道题记录一下&#xff0c;加上自己的理解&#xff0c;方便日后查看! 对于这一类的dp习惯性的都初始化dp的大…

好用的数据恢复软件EasyRecovery2023最新版

实用的数据恢复软件有什么&#xff1f;电脑中的数据文件对很多的小伙伴来说都是非常重要的&#xff0c;在下载安装新的软件设备时都需要非常谨慎&#xff0c;一旦碰到一些病毒就可能会导致文件丢失&#xff0c;想要恢复这些文件并不是很容易&#xff0c;需要使用专业的数据恢复…

proxy

let obj new Proxy({},{get: function(target,propKey,receiver) {console.log(获取的时候会被拦截)console.log(target,propKey,receiver)return Reflect.get(target,propKey,receiver)},set: function(target,propKey,value,receiver) {console.log(设置时被拦截);console.l…

Java开发:JVM篇-类加载内存分析

一、类加载器 A、类加载器的作用 将class文件字节码内容加载到内存中&#xff0c;并将这些静态数据结构转换成方法区的运行时数据结构&#xff0c;然后在堆中生成一个代表这个类的java.lang.Class对象&#xff0c;作为方法区中类数据的访问入口。 B、类缓存 标准的JavaSE类…

收到公安部门的致谢信,顶象业务安全“反诈”再接再厉

12月1日&#xff0c;《反电信网络诈骗法》正式施行&#xff0c;为反电信网络诈骗工作提供有力法律支撑。自2021年以来&#xff0c;顶象业务安全情报通过多渠道的风险数据以及多维度深度挖掘与分析&#xff0c;助力对电信诈骗分子的精准防控&#xff0c;已为多个监管机构的反电信…

小程序开发--- 03组件

小程序中组件的分类主要有以下9类&#xff1a; 常用的视图容器类是组件有&#xff1a; 1.view : 这是普通的视图区域&#xff0c;类似于HTML中的div&#xff0c;是一个块级元素&#xff0c;常用来实现页面的布局效果 2. scroll-view: 是一个可以上下或者左右滚动的区域&#x…

数据结构—set集合

文章目录一、HashSet集合1.HashSet集合的特点2.HashSet常用方法二、LinkedHashSet集合LinkedHashSet集合的特点三、TreeSet集合1.TreeSet集合的特点2.TreeSet的基本使用四、HashSet、LinkedHashSet、TreeSet的使用场景HashSet:LinkedHashSet&#xff1a;TreeSet:五、list和set集…

flutter 自定义加载中间页 loading 菊花组件的封装

flutter 自定义加载中间页前言LoadingStateWidget 封装思路总结前言 在日常移动开发中&#xff0c;很多时候需要我们添加一个加载中间页&#xff0c;即加载中&#xff0c;加载失败&#xff0c;加载重试&#xff0c;加载完成等功能&#xff0c;这样可以避免在无网或者弱网情况下…

《痞子衡嵌入式半月刊》 第 64 期

痞子衡嵌入式半月刊&#xff1a; 第 64 期 这里分享嵌入式领域有用有趣的项目/工具以及一些热点新闻&#xff0c;农历年分二十四节气&#xff0c;希望在每个交节之日准时发布一期。 本期刊是开源项目&#xff08;GitHub: JayHeng/pzh-mcu-bi-weekly&#xff09;&#xff0c;欢…

【GD32F427开发板试用】IAR 环境移植freertos

本篇文章来自极术社区与兆易创新组织的GD32F427开发板评测活动&#xff0c;更多开发板试用活动请关注极术社区网站。作者&#xff1a;andeyqi freertos移植适配 社区之前已经有同学移植适配freertos&#xff0c;在GD32F427上跑了起来&#xff0c;之前的帖子是在MDK环境下适配的…

FITC-PEG-FA,Folic acid-PEG-Fluorescein,叶酸PEG荧光素

●外观以及性质&#xff1a; FITC-PEG-FA是一种绿色荧光叶酸-PEG衍生物&#xff0c;其激发/发射波长为~490 nm/~520 nm。荧光素标记的PEG叶酸是一种线性分子&#xff0c;叶酸连接到PEG链的一端&#xff0c;荧光素染料连接到另一个PEG端。叶酸也被称为维生素M、维生素B9或叶酸。…

什么?Coolbpf 不仅可以远程编译,还可以发现网络抖动!

近日&#xff0c;在 2022 云栖大会龙蜥峰会 eBPF & Linux 稳定性专场上&#xff0c;来自 eBPF 技术探索 SIG Maintainer 的毛文安分享了《Coolbpf 的应用实践》技术演讲&#xff0c;以下为本次演讲内容&#xff1a; 一、为什么要支持可移植&#xff1f; 随着 BPF 技术的发…