Fluent-Python-第16章:协程

协程

如果 Python 书籍有一定的指导作用,那么(协程就是)文档最匮乏、最鲜为人知的 Python 特性,因此表面上看是最无用的特性。
——David Beazley, Python 图书作者

在“生成器”章节中我们认识了 yield 语句。但 yield 的作用不只是在生成器运行过程中返回一个值,还可以从调用方拿回来一个值(.send(datum)),甚至一个异常(.throw(exc))。
由此依赖,yield 语句就成为了一种流程控制工具,使用它可以实现协作式多任务:协程可以把控制器让步给中心调度程序,从而激活其它的协程。

从根本上把 yield 视作控制流程的方式,这样就好理解协程了。

本章涵盖以下话题:

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

协程的四种状态:

  • GEN_CREATED: 等待开始执行
  • GEN_RUNNING: 解释器正在执行
  • GEN_SUSPENDED: 在 yield 表达式处暂停
  • GEN_CLOSED: 执行结束
# 最简单的协程使用演示
from inspect import getgeneratorstate

def simple_coroutine():
    # GEN_RUNNING 状态
    print("Coroutine started")
    x = yield
    print("Couroutine received:", x)

my_coro = simple_coroutine()
print(getgeneratorstate(my_coro)) # GEN_CREATED
next(my_coro)                     # “预激”(prime)协程,使它能够接收来自外部的值
print(getgeneratorstate(my_coro)) # GEN_SUSPENDED
try:
    my_coro.send(42)
except StopIteration as e:
    print('StopIteration')
print(getgeneratorstate(my_coro)) # GEN_CLOSED
# 产出多个值的协程
def async_sum(a=0):
    s = a
    while True:
        n = yield s
        s += n

asum = async_sum()
next(asum)
for i in range(1, 11):
    print(i, asum.send(i))
asum.close()                 # 如果协程不会自己关闭,我们还可以手动终止协程
asum.send(11)
1 1
2 3
3 6
4 10
5 15
6 21
7 28
8 36
9 45
10 55



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

StopIteration                             Traceback (most recent call last)

<ipython-input-13-0daab1220103> in <module>()
     11     print(i, asum.send(i))
     12 asum.close()                 # 如果协程不会自己关闭,我们还可以手动终止协程
---> 13 asum.send(11)


StopIteration: 

协程手动终止的注意事项:
调用 gen.closed() 后,生成器内的 yield 语句会抛出 GeneratorExit 异常。如果生成器没有处理这个异常,或者抛出了 StopIteration 异常(通常是指运行到结尾),调用方不会报错。
如果收到 GeneratorExit 异常,生成器一定不能产出值,否则解释器会抛出 RuntimeError 异常。生成器抛出的其他异常会向上冒泡,传给调用方。

协程内异常处理的示例见官方示例 Repo

yield from

在协程中,yield from 语句的主要功能是打开双向通道,把外层的调用方与内层的子生成器连接起来,这样二者可以直接发送和产出值,还可以直接传入异常,而不用在位于中间的协程中添加大量处理异常的样板代码。
这种夹在中间的生成器,我们称它为“委派生成器”。

子生成器迭代结束后返回(return)的值,会交给 yield from 函数。

注意:yield from 语句会预激生成器,所以与用来预激生成器的装饰器不能放在一起用,否则会出问题。

# 委派生成器
def async_sum(a=0):
    s = a
    while True:
        try:
            n = yield s
        except Exception as e:
            print('Caught exception', e)
            return s
        s += n

def middleware():
    x = yield from async_sum()
    print('Final result:', x)
        
asum = middleware()
next(asum)
for i in range(1, 11):
    print(asum.send(i))

_ = asum.throw(ValueError)
1
3
6
10
15
21
28
36
45
55
Caught exception 
Final result: 55



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

StopIteration                             Traceback (most recent call last)

<ipython-input-16-27762d9b83ae> in <module>()
     19     print(asum.send(i))
     20 
---> 21 _ = asum.throw(ValueError)


StopIteration: 

关于“任务式”协程,书中给出了一个简单的例子,用于执行离散事件仿真,仔细研究一下可以对协程有个简单的认识。

posted @   superzzh  阅读(5)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 25岁的心里话
点击右上角即可分享
微信分享提示