VB.net 2010 视频教程 VB.net 2010 视频教程 python基础视频教程
SQL Server 2008 视频教程 c#入门经典教程 Visual Basic从门到精通视频教程
当前位置:
首页 > temp > python入门教程 >
  • 【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

__EOF__

 
  • 本文作者: Zinc233
  • 本文链接: https://www.cnblogs.com/Zinc233/p/FluentPython_S16.html

    
    相关教程