Python 学习笔记 关于协程

码林磷火
• 阅读 1211

协程

定义:协程是指一个过程,这个过程与调用方协作,产出由调用方提供的值。(协程中必定含有一条yield语句)

协程与生成器类似,都是定义体内包含yield关键字的函数。不过,在协程中,yield通常出现在表达式的右边(例如,data = yield),可以产出值,也可以不产出。

  • 生成器不可以返回值,如果生成器中给return语句提供值,会抛出SyntaxError异常;
  • python新引入yield from 语句,可以把复杂的生成器重构成小型的嵌套生成器,省去了大量样板代码。

三个方法:

  • . send() 方法,可以让调用方给协程发送数据,发送的数据会成为协程函数中 yield 表达式的值。
  • .throw() 方法,可以让调用方抛出异常
  • .close() 方法,可以让调用方终止协程

四个状态:

  • 'GEN_CREATED' 等待开始执行
  • 'GEN_RUNNING' 解释器正在执行
  • 'GEN_SUSPENDED' 在yield表达式处暂停
  • 'GEN_CLOASED' 执行结束

协程只能处于这四个状态中的一个,当前状态可以由 inspect.getgeneratorstate(...)函数获取

因为send() 方法的参数会成为暂停的yield表达式的值,所以,仅当协程处于暂停状态时才能调用send()方法

协程需要被预激,预激是通过next()函数进行

给协程添加预激装饰器 functools.wraps(),可以省去协程的预激过程。

yield from

在生成器gen中使用yield from subgen()时,subgen()会得到当前的控制权,把产出的值传给gen的调用方,即调用方可以直接跳过gen控制subgen。当subgen得到控制权时,gen会阻塞,同时等待subgen终止。

一个小例子:

def chain(*iters):
    for iter in iters:
        yield from iter

lst_1 = 'abc'
lst_2 = '987'
print(list(chain(lst_1, lst_2)))

运行结果:

['a', 'b', 'c', '9', '8', '7']

这个例子还可以改写为:

def chain():
    yield from 'abc'
    yield from '987'

输出结果是一样的。

yield from 的主要功能是打开双向通道,把最外层的调用方与最内层的子生成器连接起来,这样,二者可以直接发送和产生值,甚至可以直接传入异常。

一个复杂的例子,计算中学生的平均身高和体重:

from collections import namedtuple

Result = namedtuple('Result', 'count average')


# 子生成器
def averager():  # <1>
    total = 0.0
    count = 0
    average = None
    while True:
        term = yield  # <2>
        if term is None:  # <3>
            break
        total += term
        count += 1
        average = total/count
    return Result(count, average)  # <4>


# 委派生成器
def grouper(results, key):  # <5>
    while True:  # <6>
        results[key] = yield from averager()  # <7>


# 客户端代码,即调用端
def main(data):  # <8>
    results = {}
    for key, values in data.items():
        group = grouper(results, key)  # <9>
        next(group)  # <10>
        for value in values:
            group.send(value)  # <11>
        group.send(None)  # important! <12>

    # print(results)  # uncomment to debug
    report(results)


# 输出报告
def report(results):
    for key, result in sorted(results.items()):
        group, unit = key.split(';')
        print('{:2} {:5} averaging {:.2f}{}'.format(
              result.count, group, result.average, unit))


data = {
    'girls;kg':
        [40.9, 38.5, 44.3, 42.2, 45.2, 41.7, 44.5, 38.0, 40.6, 44.5],
    'girls;m':
        [1.6, 1.51, 1.4, 1.3, 1.41, 1.39, 1.33, 1.46, 1.45, 1.43],
    'boys;kg':
        [39.0, 40.8, 43.2, 40.8, 43.1, 38.6, 41.4, 40.6, 36.3],
    'boys;m':
        [1.38, 1.5, 1.32, 1.25, 1.37, 1.48, 1.25, 1.49, 1.46],
}


if __name__ == '__main__':
    main(data)

运行结果:

 9 boys  averaging 40.42kg
 9 boys  averaging 1.39m
10 girls averaging 42.04kg
10 girls averaging 1.43m
  • 委派生成器grouper()只是起到一个传输数据的作用,没有进行任何的数据处理。

生成器中都有一个无限循环 while True: 这个无限循环表明,只要调用方不断把值发送给这个协程,它就会一直接收值,然后生成结果。该循环结束条件:

  • 调用方在协程上显式调用 .close() 方法,
  • 或者没有对协程的引用,而被垃圾回收程序回收时,这个协程才会终止。

终止协程的方法

generator.close()

该方法致使生成器在暂停的 yield 表达式处抛出 GeneratorExit 异常。

  • 如果生成器处理了这个异常,生成器一定不能产生值,否则解释器会抛出RuntimeError异常。
  • 如果生成器没有处理这个异常,或者抛出StopIteration异常,即生成器已经运行到最后,调用方也不会报错。
点赞
收藏
评论区
推荐文章
风斗 风斗
4年前
Kotlin 协程中,关于 runBlocking, launch ,withContext ,async,doAsync 之间的简单区别
引入大佬的话,Kotlin的协程,本质上是一个线程框架,它可以方便的切换线程的上下文(如主线程切换到子线程/子线程切回主线程)。而平时我们要想在AndroidStudio使用协程,先要在gradle引入协程依赖:implementation"org.jetbrains.kotlinx:kotlinxcoroutinescore:1.3.3"
Wesley13 Wesley13
3年前
go 协程
packageutilsimport("bytes""fmt""runtime""strconv")_/\__获取协程__ID\/_funcGetGoroutineID(){b:make(\\byte,64)b\b\:runtime.Stack(b,false)\b\
Stella981 Stella981
3年前
Gevent简明教程
1、前述进程线程协程异步并发编程(不是并行)目前有四种方式:多进程、多线程、协程和异步。多进程编程在python中有类似C的os.fork,更高层封装的有multiprocessing标准库多线程编程python中有Thread和threading异步编程在linux下主要有三种实现selec
Stella981 Stella981
3年前
Coroutine in Java协程
转自 https://segmentfault.com/a/1190000006079389?fromgroupmessage&isappinstalled0说到协程(Coroutine)
Stella981 Stella981
3年前
Goroutine(协程)为何能处理大并发?
简单来说:协程十分轻量,可以在一个进程中执行有数以十万计的协程,依旧保持高性能。进程、线程、协程的关系和区别:进程拥有自己独立的堆和栈,既不共享堆,亦不共享栈,进程由操作系统调度。线程拥有自己独立的栈和共享的堆,共享堆,不共享栈,线程亦由操作系统调度(标准线程是的)。协程和线程一样共享堆
Wesley13 Wesley13
3年前
Go 并发
Go并发并发指的是同时处理多个任务的能力。并行指的是并行处理多个任务的能力。并行不一定加快运行速度,因为并行组件之间可能需要互相通信。Go中使用协程,信道来处理并发。协程Go中主要通过协程实现并发。协程是与其他函数或方法一起并发运行的函数或方法,协程可以看作是轻量级线程,但是创建成本更小,我们经常
Stella981 Stella981
3年前
Python 协程与 Go 协程的区别(二)
👆“Python猫”,一个值得加星标的公众号花下猫语:今天继续分享协程系列的第二篇。万字长文,收藏起来慢慢读吧。PS:原文中有挺多参考链接,微信不能很好保留。故建议阅读原文。作者:lgj\_bky(经作者授权转载)原文:https://www.cnblogs.com/lgjbky/p/10838035.html写在前面
Easter79 Easter79
3年前
Swoole2.0内置协程并发测试
Swoole2.0是一个革命性的版本,它内置了协程的支持。与Go语言协程不同,Swoole协程完全不需要开发者添加任何额外的关键词,直接以过去最传统的同步阻塞模式编写代码,底层自动进行协程调度实现异步IO。使并发编程变得非常简单。最新的版本中,内置协程已支持PHP7,同时兼具了性能和并发能力,Swoole的强大超乎想象。本文基于Github最新的Sw
Android Kotlin 协程初探 | 京东物流技术团队
1它是什么(协程和Kotlin协程)1.1协程是什么维基百科:协程,英文Coroutine\2为什么选择它(协程解决什么问题)异步场景举例:1.第一步:接口获取当前用户token及用户信息2.第二步:将用户的昵称展示界面上3.第三步:然后再通过这个toke