【Python】笔记:协程

协程

用作协程的生成器的基本行为

  • 协程使用生成器函数定义: 定义体中有 yield 关键字
def simple_coroutine():
    print('-> coroutine start')
    x = yield  # 因为 yield 右边没有值, 所以产出 None
    print('-> coroutine received: ', x)

coro = simple_coroutine()
print(1, repr(coro))
next(coro)  # 协程运行到第一个 yield 处暂停, 等待发送数据
coro.send(233)  # 协程恢复, 运行至下一个 yield 或者终止
1 <generator object simple_coroutine at 0x000001C7B11C8930>
-> coroutine start
-> coroutine received:  233



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

StopIteration                             Traceback (most recent call last)

Cell In [4], line 9
      7 print(1, repr(coro))
      8 next(coro)
----> 9 coro.send(233)


StopIteration: 

协程有四种状态, 可以使用 inspect.getgeneratorstste(...) 来获取状态

状态 描述
GEN_CREATED 等待开始执行
GEN_RUNNING 解释器正在执行(只有在多线程应用中才能看到这个状态, (或在生成器对象自己身上调用 getgeneratorstste, 不过没啥用))
GEN_SUSPENDED yield 处暂停
GEN_CLOSED 执行结束

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

因此, 一开始要用 next(coro) 激活协程 (这一步称为 预激 (prime) 协程)

coro.send(None) 效果一样, 但如果发送除 None 以外的值, 会抛出 TypeError

coro = simple_coroutine()
coro.send(233)
---------------------------------------------------------------------------

TypeError                                 Traceback (most recent call last)

Cell In [5], line 2
      1 coro = simple_coroutine()
----> 2 coro.send(233)


TypeError: can't send non-None value to a just-started generator
from inspect import getgeneratorstate

def simple_coroutine2(a):
    print('-> Started: a =', a)
    b = yield a
    print('-> Received: b =', b)
    c = yield a + b
    print('-> Received: c =', c)

coro2 = simple_coroutine2(14)

print(1, getgeneratorstate(coro2))
print(2, next(coro2))  # 预激, 执行到第一个 yield, 并产出 a
print()
print(3, getgeneratorstate(coro2))
print(4, coro2.send(66))  # 执行到第二个 yield, 并产出 a + b
print()
print(5, coro2.send(233))  # 执行结束, 抛出异常
1 GEN_CREATED
-> Started: a = 14
2 14

3 GEN_SUSPENDED
-> Received: b = 66
4 80

-> Received: c = 233



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

StopIteration                             Traceback (most recent call last)

Cell In [9], line 18
     16 print(4, coro2.send(66))
     17 print()
---> 18 print(5, coro2.send(233))


StopIteration: 

示例:使用协程计算移动平均值

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

aver = averager()
next(aver)
print(aver.send(233))
print(aver.send(234))
print(aver.send(235))

233.0
233.5
234.0

预激协程的装饰器

from functools import wraps

def coroutine(func):

    @wraps(func)
    def primer(*args, **kwargs):
        gen = func(*args, **kwargs)
        next(gen)
        return gen

    return primer
@coroutine
def averager():
    total = 0
    count = 0
    average = None

    while True:
        term = yield average
        total += term
        count += 1
        average = total / count

coro3 = averager()
print(coro3.send(233))
print(coro3.send(234))
print(coro3.send(235))
233.0
233.5
234.0

终止协程和异常处理

通过 throw()close() 显式将异常抛给协程, 让其处理/退出

  • generator.throw(exc_type[, exc_value[, traceback]]) 致使生成器在暂停处抛出指定异常, 如果生成器处理了异常, 那么代码会向前执行到下一个 yield 表达式, 而产生的值会成为调用 generator.thow 方法得到的返回值

  • generator.close() 致使生成器在暂停处抛出 GeneratorExit 异常, 如果生成器没有处理这个异常, 或抛出了 GeneratorExit 异常, 调用方不会报错; 如果收到 GeneratorExit 异常, 生成器一定不能产出值, 否则解释器会抛出 RuntimeError

class DemoException(Exception):
    '''一个新的异常'''

def demo_exc_handing():
    print('-> coroutine started')

    while True:
        try:
            x = yield
        except DemoException:
            print('-> DemoException handled...')
        else:
            print('-> coroutine received: {!r}'.format(x))
    
    raise RuntimeError("This line should never run")
exc_coro = demo_exc_handing()
next(exc_coro)
exc_coro.send(233)
exc_coro.send(666)
exc_coro.throw(DemoException)
exc_coro.send(999)
exc_coro.close()
-> coroutine started
-> coroutine received: 233
-> coroutine received: 666
-> DemoException handled...
-> coroutine received: 999
exc_coro = demo_exc_handing()
next(exc_coro)
exc_coro.send(233)
exc_coro.throw(ZeroDivisionError)
-> coroutine started
-> coroutine received: 233



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

ZeroDivisionError                         Traceback (most recent call last)

Cell In [14], line 4
      2 next(exc_coro)
      3 exc_coro.send(233)
----> 4 exc_coro.throw(ZeroDivisionError)


Cell In [12], line 9, in demo_exc_handing()
      7 while True:
      8     try:
----> 9         x = yield
     10     except DemoException:
     11         print('-> DemoException handled...')


ZeroDivisionError: 
# 扫尾工作
class DemoException(Exception):
    '''一个新的异常'''

def demo_exc_handing():
    print('-> coroutine started')
    try:
        while True:
            try:
                x = yield
            except DemoException:
                print('-> DemoException handled...')
            else:
                print('-> coroutine received: {!r}'.format(x))
    finally:
        print('-> coroutine ending')

让协程返回值

from collections import namedtuple

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

def averager():
    total = 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)
coro4 = averager()
next(coro4)
coro4.send(10)
coro4.send(30)
coro4.send(None)  # 错误示范
---------------------------------------------------------------------------

StopIteration                             Traceback (most recent call last)

Cell In [17], line 5
      3 coro4.send(10)
      4 coro4.send(30)
----> 5 coro4.send(None)  # 错误示范


StopIteration: Result(count=2, average=20.0)

此例中, 发送 None 会导致循环终止, 协程结束, 抛出 StopIteration

Result() 会保存在 StopIteration.value

coro5 = averager()
next(coro5)
coro5.send(10)
coro5.send(20)
try:
    coro5.send(None)
except StopIteration as exc:
    result = exc.value

print(result)
Result(count=2, average=15.0)

使用 yield from

yield from x 会调用 iter(x), 取得迭代器

# 复习一下 yield from 的基本用法
def gen():
    for c in 'AB':
        yield c
    for i in range(1, 3):
        yield i

print(list(gen()))
['A', 'B', 1, 2]
def gen():
    yield from 'AB'
    yield from range(1, 3)

print(list(gen()))
['A', 'B', 1, 2]
术语 说明
委派生成器 包含 yield from <iterable> 表达式的生成器函数
子生成器 yield from 表达式中 <iterable> 部分获取的生成器
调用方/客户端 调用委派生成器的客户端代码
# 例:计算平均数们
from collections import namedtuple

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

# 子生成器
def averager():
    total = 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 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)  # 终止当前的 averager
    
    # print(results)
    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 = {
    'human;kg': [65.78331, 71.51521, 69.39874, 68.2166, 67.78781, 68.69784, 69.80204, 70.01472, 67.90265, 66.78236, 66.48769, 67.62333, 68.30248, 67.11656, 68.27967, 71.0916, 66.461, 68.64927, 71.23033, 67.13118, 67.83379, 68.87881, 63.48115, 68.42187, 67.62804],
    'human;cm': [112.9925, 136.4873, 153.0269, 142.3354, 144.2971, 123.3024, 141.4947, 136.4623, 112.3723, 120.6672, 127.4516, 114.143, 125.6107, 122.4618, 116.0866, 139.9975, 129.5023, 142.9733, 137.9025, 124.0449, 141.2807, 143.5392, 97.90191, 129.5027, 141.8501]
}  # 注明一下, 数据来源于百度飞浆

main(data)
25 human averaging 130.31cm
25 human averaging 68.18kg

摘自 PEP 380

Any values that the iterator yields are passed directly to the caller.

Any values sent to the delegating generator using send() are passed directly to the iterator. If the sent value is None, the iterator’s __next__() method is called. If the sent value is not None, the iterator’s send() method is called. If the call raises StopIteration, the delegating generator is resumed. Any other exception is propagated to the delegating generator.

Exceptions other than GeneratorExit thrown into the delegating generator are passed to the throw() method of the iterator. If the call raises StopIteration, the delegating generator is resumed. Any other exception is propagated to the delegating generator.

If a GeneratorExit exception is thrown into the delegating generator, or the close() method of the delegating generator is called, then the close() method of the iterator is called if it has one. If this call results in an exception, it is propagated to the delegating generator. Otherwise, GeneratorExit is raised in the delegating generator.

The value of the yield from expression is the first argument to the StopIteration exception raised by the iterator when it terminates.

return expr in a generator causes StopIteration(expr) to be raised upon exit from the generator.

posted @ 2022-12-03 21:19  Zinc233  阅读(35)  评论(0编辑  收藏  举报