首页 > 编程语言 >【Python】笔记:协程

【Python】笔记:协程

时间:2022-12-03 21:35:16浏览次数:62  
标签:协程 exc coroutine Python coro yield send 笔记 print

协程

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

  • 协程使用生成器函数定义: 定义体中有 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.

标签:协程,exc,coroutine,Python,coro,yield,send,笔记,print
From: https://www.cnblogs.com/Zinc233/p/FluentPython_S16.html

相关文章

  • 【Python】笔记:上下文管理器和else快
    上下文管理器和else快类似于then的elsefor...else...仅在for循环运行完毕后运行else,不能被breakwhile...else...仅在while条件为false而退出后运行......
  • 【Python】笔记:可迭代的对象、迭代器和生成器
    可迭代的对象、迭代器和生成器importreimportreprlibRE_WORD=re.compile('\w+')classSentence_v1:def__init__(self,text):self.text=text......
  • Vue2(笔记15) - Vue核心 - 过滤器
    可学可不学,可用可不用过滤器需求:把一个时间戳格式化成可读的年月日时间;需要引入一个dayjs 的 JS库,专门用来处理时间的;​​dayjs在这可以下载​​<scriptsrc="./res/vue.......
  • 【Python】笔记:接口:从协议到抽象基类
    S11接口:从协议到抽象基类#random.shuffle就地打乱fromrandomimportshufflel=list(range(10))shuffle(l)print(l)shuffle(l)print(l)[0,6,3,2,4,8,......
  • 【Python】笔记:正确重载运算符
    正确重载运算符一元运算符-(__neg__)+(__pos__)最好返回self的副本~(__invert__)对整数位按位取反(~x==-(x+1))print(~2)-3中辍运算符+fromarray......
  • 回文链表-python
    问题:给你一个单链表的头节点 head ,请你判断该链表是否为回文链表。如果是,返回 true ;否则,返回 false 。思考:对称结构要想到stack方案一:双指针法将节点值赋值到数组......
  • Vue2(笔记14) - Vue核心 - 表单数据收集
    表单数据收集表单数据收集是最常用的逻辑;<divid="root"><[email protected]="demo">账号:<inputtype="text"v-model.trim="userInfo.account"><br><br>......
  • Python内容写入文件
       Python允许你将内容写入文件,方式与使用print()函数将字符串‘写’到屏幕上类似,但是,如果打开文件时用读模式,就不能写入,你需要以纯文本模式或添加纯文本模式打开该文......
  • Python 跳动的小球
    一、实验内容:跳动的小球游戏介绍二、实验对象:《零基础学Python》第13章Pygame游戏编程实例01用以下代码创建一个游戏弹窗:导入pygame模块并且用init()方法初始化,设置窗......
  • n202_python数据类型和数据结构
    3.数据类型和数据结构python的数据类型大致可以分为两种:python自带的内置数据类型和第三方扩展包中的数据类型。其中,python自带的内置数据类型可以分为两种:可变数据类......