Python 学习笔记 关于协程

协程

定义:协程是指一个过程,这个过程与调用方协作,产出由调用方提供的值。(协程中必定含有一条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异常,即生成器已经运行到最后,调用方也不会报错。
    原文作者:逸远尘红
    原文地址: https://segmentfault.com/a/1190000016249331
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞