Python3 协程(Coroutines)

协程

生成器相比,协程中也运用到了yield关键字,但具备比生成器更多的功能。

协程的基本用法

下面通过一个例子描述协程的基本用法。

def simple_coro(a):
    print('started: a = ', a)
    b = yield a
    print('recieved: b = ', b)
    c = yield a + b
    print('received: c = ', c)

coro = simple_coro(14)
print(inspect.getgeneratorstate(coro))
print(next(coro))
print(inspect.getgeneratorstate(coro))
print(coro.send(20))

try:
    coro.send(15)
except StopIteration:
    print(inspect.getgeneratorstate(coro))

"""output
GEN_CREATED
started: a =  14
14
GEN_SUSPENDED
recieved: b =  20
34
received: c =  15
GEN_CLOSED
"""

在协程中,yield右侧的用法和生成器类似,区别yield左侧=的赋值。

协程的状态

协程具有四种状态分别为:

  1. GEN_CREATED
  2. GEN_RUNNING
  3. GEN_SUSPENDED
  4. GEN_CLOSED

当调用simple_coro2(14)函数后,我们得到了协程coro。此时coro的状态为’GEN_CREATED’(可以通过标准库中的inspect.getgeneratorstate(coro)得到该状态)。
在执行next(coro)后,simple_coro函数开始执行(此时coro状态为GEN_RUNNING),打印出’started: a = 14’,直到遇到第一个yield暂时挂起,此时coro状态为GEN_SUSPENDED,在yield a处等待下一次调用者触发协程。这里的next(coro)类似,将获取到协程中yield a中的a值(14)。
在执行coro.send(20)时,coro的状态从GEN_SUSPENDED改变为GEN_RUNNING,并将send函数中的参数20赋值到yield左边的变量b,因此程序会打印出’recieved: b = 20’。协程继续运行,直到yield a + b, 将a + b的值返回给调用者,因此’coro.send(20)‘的值为34。
此时协程coro在yield a + b处暂时停止,如再次调用’coro.send(15)’,coro中的变量c将会接收这个值,coro此时运行到结尾,会抛出StopIteration的异常, 此时协程coro的状态则为GEN_CLOSED。

协程的关闭与抛出异常

如果协程在运行中遇到没有被try/except捕获的异常,协程将立即停止进入GEN_CLOSED状态,并将该异常传回调用者(send或next)。

import inspect


def averager():
    total = 0.0
    count = 0
    average = None
    while True:
        term = yield average
        total += term
        count += 1
        average = total / count


coro_avg = averager()
next(coro_avg)
print(coro_avg.send(10))
print(coro_avg.send(20))
print(coro_avg.send(30))

try:
    coro_avg.send('33')
except Exception as e:
    print(e)

print(inspect.getgeneratorstate(coro_avg))

"""output
10.0
15.0
20.0
unsupported operand type(s) for +=: 'float' and 'str'
GEN_CLOSED
"""

协程由于接受了字符串’33’,在进行’total += term’语句时触发异常,由于在协程中没有捕获该异常,则将该异常传回send,我们在send处捕获该异常并打印出来。此时,协程coro_avg已经停止。

协程的close()与throw()

协程中有两种可以从调用方发送给协程的异常:

  1. generator.throw(exc_type[, exc_value[, traceback]])
    调用方向协程发送异常,协程会在等待中的yield处抛出异常,如果该异常在该yield处被捕获,协程会继续运行,generator.throw的返回值则是下一个yield后的值。如果该异常未被捕获,协程将立即停止,将异常传递回generator.throw。
import inspect


class DemoException(Exception):
    pass


def demo_exc_handling():
    print('coroutine started')
    while True:
        try:
            x = yield 200
        except DemoException:
            print('DemoException handled. Continuing...')

        else:
            print('coroutine received: {!r}'.format(x))


coro_exc = demo_exc_handling()
next(coro_exc)
coro_exc.send('hey')
print(coro_exc.throw(DemoException))
print(inspect.getgeneratorstate(coro_exc))
try:
    coro_exc.throw(ZeroDivisionError, 500)
except ZeroDivisionError as e:
    print(e)
print(inspect.getgeneratorstate(coro_exc))

"""output
coroutine started
coroutine received: 'hey'
DemoException handled. Continuing...
200
GEN_SUSPENDED
500
GEN_CLOSED
"""
  1. generator.close()
    协程会在等待中的yield处抛出GeneratorExit异常,如果在协程中没有捕获该异常,协程将立刻关闭进入到GEN_CLOSED状态,而且GeneratorExit也不会传递到调用方的generator.close函数。当在协程中捕获GeneratorExit异常时,协程将继续运行如果此时抛出StopIteration异常或是再次执行yield语句时将会抛出RuntimeError异常给调用方,如果在捕获GeneratorExit后抛出其他类型的异常,该异常将会传递到调用方,并且协程也会立刻停止。

协程的返回值

在Python3.3之后,在协程中允许有返回值(Return)。

def averager():
    total = 0.0
    count = 0
    average = None
    while True:
        term = yield
        if term is None:
            break
        total += term
        count += 1
        average = total / count

    return average

coro_avg = averager()
next(coro_avg)
coro_avg.send(10)
coro_avg.send(20)
coro_avg.send(30)
try:
    coro_avg.send(None)
except StopIteration as e:
    print(e.value)

"""output
20.0
"""

调用方通过捕获协程正常结束后抛出的StopIteration的value值得到协程Return的值,因此如果让协程关闭或是抛出异常(调用close或是throw函数),则协程会提前抛出所对应的异常,不会执行Return的操作。

yield from

yield from也同样是Python3.3之后新加入的语法,yield from具备处理嵌套生成器的相关功能,Python3中的迭代器和生成器
除此之外,yield from更像是一种代理协程,它存在于调用方和yield from后的协程,它可以把调用方的close throw send等直接传递到里层的协程中,并可以处理里层协程抛出的异常返回值等,它就像是调用方和里层协程中的一种通道道。

from collections import namedtuple

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


def averager():
    total = 0.0
    count = 0
    average = None
    while True:
        term = yield
        if term is None:
            break
        total += term
        count += 1
        average = total / count

    return Result(count, average)


def grouper(results, key):
    while True:
        results[key] = yield from averager()


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], }


def main(data):
    results = {}
    for key, values in data.items():
        group = grouper(results, key)
        next(group)
        for value in values:
            group.send(value)
        group.send(None)

    report(results)


main(data)
"""output
 9 boys  averaging 40.42kg
 9 boys  averaging 1.39m
10 girls averaging 42.04kg
10 girls averaging 1.43m
"""

在这里插入图片描述
在上述例子中,main作为调用方,average为里层的协程,grouper作为为中间的main和average中间的协程代理,可以传递send、yield等。

yield from的实现

摘自Fluent Python
在这里插入图片描述

在这里插入图片描述

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值