- 教程
- 文档
- 教育
【免费下载链接】interpy-zh
📘《Python进阶》(Intermediate Python - Chinese Version)
本文是《Python进阶》(interpy-zh 中文译本)"数据结构"章节中协程主题的深度指南。协程与生成器共享yield语法,却扮演着截然相反的角色:生成器是数据的生产者,协程则是数据的消费者。读完本文,你将掌握用next()启动协程、用send()向协程喂数据、用close()关闭协程的完整流程,并能读懂仓库中 code/2.7/23_coroutine.py 的实战实现,为后续学习异步编程打下基础。
协程与生成器:同源而殊途
在 Python 中,协程(Coroutine)和生成器(Generator)长得非常像——它们都使用yield关键字、都以函数形式编写,但两者的定位有本质区别:
- 生成器是数据的生产者:它通过
yield源源不断地"吐出"值,供外部消费; - 协程是数据的消费者:它通过
yield接收外部"喂入"的值,再对这些值进行加工处理。
一句话概括:同一个yield,向外产出值时它是生成器,向内接收值时它就变成了协程。
第一步:回顾生成器的创建与使用
要理解协程,先要熟悉生成器。在 book/DataStructures/generators.md 中已经系统讲解了迭代器(Iterator)、可迭代对象(Iterable)与迭代(Iteration)三者的关系。这里回顾最经典的斐波那契生成器写法:
def fib(): a, b = 0, 1 while True: yield a a, b = b, a + b配合for循环使用:
for i in fib(): print(i)这样做有两个好处:
- 快:值在迭代过程中实时产出,不需要先构建完整序列;
- 省内存:斐波那契数列的每一项都是动态生成的,而不是被一次性存储到一个列表中——这正是 book/DataStructures/generators.md 强调的"生成器最佳应用场景:你不想同一时间将所有计算出来的大量结果集分配到内存当中"。
第二步:把yield变成接收通道——grep 协程
如果我们在生成器中把yield放在表达式位置(如line = (yield)),并去掉yield后面的产出值,函数就不再向外部产出数据,而是等待外部向它发送数据——它变成了一只协程。原文档给出的 Python 版grep就是最直观的例子:
def grep(pattern): print("Searching for", pattern) while True: line = (yield) if pattern in line: print(line)对比斐波那契生成器,这里有三个关键变化:
| 对比维度 | 生成器 fib | 协程 grep |
|---|---|---|
yield的形态 | yield a(带产出值) | (yield)(裸 yield,作为表达式) |
| 数据流向 | 函数体向外产出值 | 外部向函数体注入值 |
| 角色 | 数据生产者 | 数据消费者 |
line = (yield)这行代码是整个协程的"接收口":外部发送进来的值会被yield表达式"接住"并赋值给line,随后if pattern in line会对接收到的每一行文本做模式匹配,命中则打印。整个while True循环让协程可以无限次地接收数据、处理数据,而不是处理完一次就结束。
第三步:用send()向协程喂数据
协程不再自带初始值,而是"从外部传值给它",这个传值动作由生成器协议的send()方法完成。原文档给出了完整的调用序列:
search = grep('coroutine') next(search) # output: Searching for coroutine search.send("I love you") search.send("Don't you love me?") search.send("I love coroutine instead!") # output: I love coroutine instead!逐行拆解这段代码的执行过程:
search = grep('coroutine'):调用协程函数不会立即执行函数体,只是创建一个协程对象;next(search):启动协程,函数体开始执行,先打印Searching for coroutine,随后执行到line = (yield)挂起等待;search.send("I love you"):将"I love you"发送进去,赋值给line,模式'coroutine'不在其中,不打印,循环再次挂起在yield;search.send("Don't you love me?"):同上,'coroutine'不在其中,不打印;search.send("I love coroutine instead!"):'coroutine'命中,打印I love coroutine instead!。
注意send()的语义:发送的值会被yield接收,同时send()本身也会推进协程继续执行到下一个yield挂起点——这正是它和"纯传值"不同的地方。仓库中 code/2.7/23_coroutine.py 完整复刻了这段逻辑(Python 2.7 版本,使用print语句),读者可以直接执行验证:
python code/2.7/23_coroutine.py该文件把调用过程封装进了main(),运行结果与上文输出完全一致。
第四步:为什么必须先调用next()?
原文档特别强调了一个初学者最容易困惑的问题:"发送的值会被yield接收。我们为什么要运行next()方法呢?这样做正是为了启动一个协程。"
原因在于:协程函数体内的代码并不会在创建对象时立刻执行。就像生成器一样,代码是惰性的——必须通过next()推进到第一个yield表达式处,协程才真正"就位",之后send()发送的值才有接收者。换句话说:
next(search)让协程执行到yield处并挂起;- 此后每次
send(value)都会唤醒协程,让yield表达式的值变为value,继续执行到下一个yield再次挂起。
原文档的结论是:必须通过next()方法来执行yield表达式,协程才能响应后续的send()调用。在 Python 3 中,send(None)与next()等价,也可以用来启动协程——next()本质就是send(None)的语法糖(这是生成器协议的标准行为,可在 Python 官方文档中确认)。
第五步:用close()关闭协程
当协程不再需要时,可以调用close()方法关闭它。关闭后协程将释放自身,再次对它send()会抛出StopIteration异常。原文档示例:
search = grep('coroutine') search.close()close()的作用原理是:在协程挂起的yield处抛出GeneratorExit异常,如果协程没有捕获它(本例中while True循环会因此终止),协程便正式结束。这保证了一个"永远while True"的协程能够被外部主动终止,而不会泄漏资源。
结合仓库源码:协程与生成器的完整知识地图
在《Python进阶》仓库中,协程并不是孤立的知识点,它与多个章节互为表里:
- book/DataStructures/generators.md:协程的上游知识。这里详细讲解了可迭代对象、迭代器、迭代三者的区别,以及
next()、iter()的用法和StopIteration异常的含义。理解"生成器只能迭代一次""for循环会自动捕获StopIteration"这些特性,是理解协程挂起/唤醒机制的前提; - book/DataStructures/README.md:协程所属的"数据结构"章节入口,本主题在该章节中排在生成器之后(nav_order: 2),正是"先生成器、后协程"的学习脉络;
- book/Syntax/context_managers.md:协程的应用延伸。该章节展示了用
@contextmanager装饰器配合生成器实现上下文管理器:函数体内yield f之前的代码在进入with块时执行,yield之后的代码在退出时执行——这正是把"函数暂停/恢复"能力(与协程同源的机制)用于资源管理的经典范式; - code/2.7/23_coroutine.py:协程的可执行样例。仓库 code/README.md 说明,
code/2.7/目录下的代码为 Python 2.7 专用版本,而书文中夹带的代码则保证兼容 Python 2/3,读者可按需取舍。
从协程走向更广阔的异步世界
本主题只是协程的入门。原文档最后推荐了 David Beazley 关于协程的著名演讲(A Curious Course on Coroutines and Concurrency),其中涵盖协程的分发(dispatch)、流水线(pipeline)、扇出(fan-out)等高级模式。若想继续深入,可以沿着两条路线:
- 阅读本仓库相邻章节:先巩固 book/DataStructures/generators.md 的生成器协议,再通过 book/Syntax/context_managers.md 观察
yield挂起机制在实际工程中的应用; - 对比 Python 3 的现代写法:理解"消费者协程 + 显式推进"这一经典模型后,再去看
yield from和async/await就会轻松许多——它们解决的是同一类问题,只是语法和调度方式更高级。
小结
本文完整还原了《Python进阶》协程章节的讲解脉络,并补充了仓库源码层面的印证:
- 生成器用
yield产出值,协程用(yield)接收值; next()负责启动协程并推进到第一个挂起点,是send()生效的前提;send(value)既向协程注入数据,又推进协程执行到下一个yield;close()用于主动终止一个无限循环的协程;- 实战代码见 code/2.7/23_coroutine.py,相关理论见 book/DataStructures/generators.md。
掌握"启动—喂值—挂起—再喂值—关闭"这一完整生命周期,你就真正理解了 Python 协程的核心机制,也拿到了进入异步编程世界的第一把钥匙。
- 教程
- 文档
- 教育
【免费下载链接】interpy-zh
📘《Python进阶》(Intermediate Python - Chinese Version)
相关推荐
FastAPI 依赖注入进阶:用 `yield` 编写带清理逻辑的依赖(数据库会话、退出码与作用域详解)
FastAPI 依赖注入进阶:用 yield 编写带清理逻辑的依赖(数据库会话、退出码与作用域详解) 本指南以 FastAPI 官方教程中「依赖与 yield
后端Web框架API设计verilog2factorio命令行参数详解:定制你的蓝图生成过程
verilog2factorio命令行参数详解:定制你的蓝图生成过程 verilog2factorio是一款将Verilog硬件描述语言编译为Factorio蓝
Python数据结构操作进阶指南
Python数据结构操作进阶指南 本文全面介绍了Python中高级数据结构操作技巧,涵盖列表去重与唯一性检查、字典操作与键值处理、集合运算与数据合并、数据分组与
教程文档
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考