第16章--协程

第16章–协程

协程是用户态的轻量级线程,一个线程内可以有多个协程,协程之间的切换由用户决定,不涉及线程切换跟进程切换,所以切换效率最高
进程线程的切换是系统级的,是抢占式切换,协程的切换是用户态的,是非抢占式切换,协程主动交出控制权,协程概念相关的知识可以参考廖雪峰的网站–https://www.liaoxuefeng.com/wiki/897692888725344/923057403198272

本章的内容
• 生成器作为协程使用时的行为和状态
• 使用装饰器自动预激协程
• 调用方如何使用生成器对象的 .close() 和 .throw(…) 方法控制协程
• 协程终止时如何返回值
• yield from 新句法的用途和语义
• 使用案例——使用协程管理仿真系统中的并发活动

协程的状态

协程可以是下面四个状态之一,可以用inspect.getgeneratorstate()函数来获取协程状态

  • ‘GEN_CREATED’ 等待开始执行。
  • 'GEN_RUNNING’解释器正在执行。
  • ‘GEN_SUSPENDED’ 在 yield 表达式处暂停。
  • ‘GEN_CLOSED’ 执行结束。
from inspect import getgeneratorstate
def simple_coro2(a):
    print("-> Started my_coro: a = ",a)
    b=yield a
    print("-> Received: b=",b)
    c=yield a+b
    print("-> Received: c=",c)

my_coro=simple_coro2(14)
print(getgeneratorstate(my_coro)) # GEN_CREATED
print(next(my_coro)) # -> Started my_coro: a =  14  14
print(getgeneratorstate(my_coro)) # GEN_SUSPENDED
my_coro.send(28) # -> Received: b= 28
try:
    my_coro.send(99) # -> Received: c= 99
except StopIteration as e:
    print("StopIteration is catched ") # StopIteration is catched
    print(getgeneratorstate(my_coro)) # GEN_CLOSED

文中对上述代码的流程做了一个分割
在这里插入图片描述

因为 send 方法的参数会成为暂停的 yield 表达式的值,所以,仅当协程处于暂停状态时才
能调用 send 方法,例如 my_coro.send(42)。不过,如果协程还没激活(即,状态是 ‘GEN_
CREATED’),情况就不同了。因此,始终要调用 next(my_coro) 激活协程——也可以调用
my_coro.send(None),效果一样。
my_coro.send之前没有调用next(my_coro),导致协程处于GEN_CRETED状态,此时用my_coro.send()会报错

from inspect import getgeneratorstate
def simple_coro2(a):
    print("-> Started my_coro: a = ",a)
    b=yield a
    print("-> Received: b=",b)
    c=yield a+b
    print("-> Received: c=",c)

my_coro=simple_coro2(14)
print(getgeneratorstate(my_coro)) # GEN_CREATED
my_coro.send(28) # TypeError: can't send non-None value to a just-started generator

预激协程的装饰器

自己实现一个预激协程的装饰器,装饰器中自动调用一次next,让返回的gen已经处于GEN_SUSPENEDD状态

from inspect import getgeneratorstate
from functools import wraps

def coroutine(func):
    @wraps(func)
    def primer(*args,**kwargs):
        gen=func(*args,**kwargs)
        next(gen) # 装饰器中自动调用一次next,让返回的gen已经处于GEN_SUSPENEDD状态
        return gen
    return primer

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

coro_avg=averager()
print(getgeneratorstate(coro_avg)) # GEN_SUSPENDED
#此时可以直接调用send,因为装饰器已经调用了一次next
print(coro_avg.send(10)) # 10.0
print(coro_avg.send(20)) # 15.0

终止协程和异常处理

  • generator.throw(exc_type[, exc_value[, traceback]])
    致使生成器在暂停的 yield 表达式处抛出指定的异常。如果生成器处理了抛出的异常,
    代码会向前执行到下一个 yield 表达式,而产出的值会成为调用 generator.throw 方法
    得到的返回值。如果生成器没有处理抛出的异常,异常会向上冒泡,传到调用方的上下
    文中。
  • generator.close()
    致使生成器在暂停的 yield 表达式处抛出 GeneratorExit 异常。如果生成器没有处
    理这个异常,或者抛出了 StopIteration 异常(通常是指运行到结尾),调用方不会
    报错。如果收到 GeneratorExit 异常,生成器一定不能产出值,否则解释器会抛出
    RuntimeError 异常。生成器抛出的其他异常会向上冒泡,传给调用方。
"""
Coroutine closing demonstration::

# BEGIN DEMO_CORO_EXC_1
    >>> exc_coro = demo_exc_handling()
    >>> next(exc_coro)
    -> coroutine started
    >>> exc_coro.send(11)
    -> coroutine received: 11
    >>> exc_coro.send(22)
    -> coroutine received: 22
    >>> exc_coro.close()
    >>> from inspect import getgeneratorstate
    >>> getgeneratorstate(exc_coro)
    'GEN_CLOSED'

# END DEMO_CORO_EXC_1

Coroutine handling exception::

# BEGIN DEMO_CORO_EXC_2
    >>> exc_coro = demo_exc_handling()
    >>> next(exc_coro)
    -> coroutine started
    >>> exc_coro.send(11)
    -> coroutine received: 11
    >>> exc_coro.throw(DemoException)
    *** DemoException handled. Continuing...
    >>> getgeneratorstate(exc_coro)
    'GEN_SUSPENDED'

# END DEMO_CORO_EXC_2

Coroutine not handling exception::

# BEGIN DEMO_CORO_EXC_3
    >>> exc_coro = demo_exc_handling()
    >>> next(exc_coro)
    -> coroutine started
    >>> exc_coro.send(11)
    -> coroutine received: 11
    >>> exc_coro.throw(ZeroDivisionError)
    Traceback (most recent call last):
      ...
    ZeroDivisionError
    >>> getgeneratorstate(exc_coro)
    'GEN_CLOSED'

# END DEMO_CORO_EXC_3
"""

# BEGIN EX_CORO_EXC
class DemoException(Exception):
    """An exception type for the demonstration."""

def demo_exc_handling():
    print('-> coroutine started')
    while True:
        try:
            x = yield
        except DemoException:  # <1>
            print('*** DemoException handled. Continuing...')
        else:  # <2>
            print('-> coroutine received: {!r}'.format(x))
    raise RuntimeError('This line should never run.')  # <3>
# END EX_CORO_EXC

获取生成器的return值

要想获取return的值而不是yield的值,在yield不再返回值后就会返回return的值,在调用的地方捕获StopIteration可以拿到return值

def fun():
   arr=[1,2,3,4,5,6]
   for i in arr:
      yield i
   return 666

new_gen=fun()
print(new_gen) # <generator object fun at 0x000001D886B10468>
print(next(new_gen)) # 1
print(next(new_gen)) # 2
while True:
   try:
      print(next(new_gen)) #3 4 5 6
   except StopIteration as e:
      print(e.value) #666
      break

yield from

yield from 的主要功能是打开双向通道,把最外层的调用方与最内层的子生成器连接起来,
这样二者可以直接发送和产出值,还可以直接传入异常,而不用在位于中间的协程中添加
大量处理异常的样板代码。有了这个结构,协程可以通过以前不可能的方式委托职责。

"""
A coroutine to compute a running average.

Testing ``averager`` by itself::

    >>> coro_avg = averager()
    >>> next(coro_avg)
    >>> coro_avg.send(10)
    >>> coro_avg.send(30)
    >>> coro_avg.send(6.5)
    >>> coro_avg.send(None)
    Traceback (most recent call last):
       ...
    StopIteration: Result(count=3, average=15.5)


Driving it with ``yield from``::

    >>> def summarize(results):
    ...     while True:
    ...         result = yield from averager()
    ...         results.append(result)
    ...
    >>> results = []
    >>> summary = summarize(results)
    >>> next(summary)
    >>> for height in data['girls;m']:
    ...     summary.send(height)
    ...
    >>> summary.send(None)
    >>> for height in data['boys;m']:
    ...     summary.send(height)
    ...
    >>> summary.send(None)
    >>> results == [
    ...     Result(count=10, average=1.4279999999999997),
    ...     Result(count=9, average=1.3888888888888888)
    ... ]
    True

"""

# BEGIN YIELD_FROM_AVERAGER
from collections import namedtuple

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


# the subgenerator
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>


# the delegating generator
def grouper(results, key):  # <5>
    while True:  # <6>
        results[key] = yield from averager()  # <7>
    # results[key] = yield from averager()
    # yield


# the client code, a.k.a. the caller
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>
            # print(results)
        group.send(None)  # important! <12>
        # print(results)

    print(results)  # uncomment to debug
    report(results)


# output report
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)

# END YIELD_FROM_AVERAGER

代码注释
1 与示例 16-13 中的 averager 协程一样。这里作为子生成器使用。
2 main 函数中的客户代码发送的各个值绑定到这里的 term 变量上。
3 至关重要的终止条件。如果不这么做,使用 yield from 调用这个协程的生成器会永远
阻塞。
4 返回的 Result 会成为 grouper 函数中 yield from 表达式的值。
5 grouper 是委派生成器。
6 这个循环每次迭代时会新建一个 averager 实例;每个实例都是作为协程使用的生成器
对象。
7 grouper 发送的每个值都会经由 yield from 处 理, 通 过 管 道 传 给 averager 实 例。
grouper 会在 yield from 表达式处暂停,等待 averager 实例处理客户端发来的值。
averager 实例运行完毕后,返回的值绑定到 results[key] 上。while 循环会不断创建
averager 实例,处理更多的值。
8 main 函数是客户端代码,用 PEP 380 定义的术语来说,是“调用方”。这是驱动一切的
函数。
9 group 是调用 grouper 函数得到的生成器对象,传给 grouper 函数的第一个参数是
results,用于收集结果;第二个参数是某个键。group 作为协程使用。
10 预激 group 协程。
11 把各个 value 传给 grouper。传入的值最终到达 averager 函数中 term = yield 那一行;
grouper 永远不知道传入的值是什么。
12 把 None 传入 grouper,导致当前的 averager 实例终止,也让 grouper 继续运行,再创
建一个 averager 实例,处理下一组值。

其他说明
• 外层for循环每次迭代会新建一个grouper实例,赋值给group变量;group是委派生成器。
• 调用 next(group),预激委派生成器 grouper,此时进入 while True 循环,调用子生成
器 averager 后,在 yield from 表达式处暂停。
• 内层 for 循环调用 group.send(value),直接把值传给子生成器 averager。同时,当前
的 grouper 实例(group)在 yield from 表达式处暂停。
• 内层循环结束后,group 实例依旧在 yield from 表达式处暂停,因此,grouper 函数定
义体中为 results[key] 赋值的语句还没有执行。
• 如果外层 for 循环的末尾没有 group.send(None),那么 averager 子生成器永远不会终止,
委派生成器 group 永远不会再次激活,因此永远不会为 results[key] 赋值。
• 外层 for 循环重新迭代时会新建一个 grouper 实例,然后绑定到 group 变量上。前一个
grouper 实例(以及它创建的尚未终止的 averager 子生成器实例)被垃圾回收程序回收。

资料参考
代码中grouper函数为什么要用while True的循环的解释,虽然看了也还是有点懵,希望有大佬可以看完跟我解释一下
https://stackoverflow.com/questions/53687825/unsure-of-why-the-stopiteration-isnt-handled-by-yield-from
官方对yield from的介绍
https://www.python.org/dev/peps/pep-0380/
其他博文
https://wenkefendou.gitbooks.io/python3-learning/content/yield_from.html

体会
yield from 是一个魔法方法,还没有接触过具体的使用场景,理解有点困难,先简单了解一下

发布了51 篇原创文章 · 获赞 7 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/qq_36267931/article/details/103065686