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:
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 25岁的心里话