Table of Contents
一、什么是协程(Coroutine)
协程可以用于很多场景,例如生成器(Generator)、上下文管理器(Context Manager)*等。 从本质上看,协程允许*代码在执行过程中被挂起,并在之后从挂起点继续执行,这是一种比函数调用更加灵活的控制流机制。
下面通过一个生产者–消费者的例子来说明协程的工作方式。
二、生产者与消费者示例
在下面的代码中,consumer 函数中包含 yield,因此它是一个生成器,也可以被视为一个简单的协程。
def consumer(): r = "" while True: n = yield r if not n: return print("[CONSUMER] Consuming %s..." % n) r = '200 OK'1. 执行流程说明
从 produce 的视角来看,协程的执行流程如下:
-
启动协程 通过
c.send(None)启动生成器,使其运行到第一个yield位置。 -
生产与消费交替执行 之后每次调用
c.send(n):consumer从yield处恢复执行- 接收生产者传入的数据
n - 执行消费逻辑
- 再次在
yield处挂起,并将结果返回给生产者
从而实现“一次生产、一次消费”的效果。
-
关闭协程 最终需要调用
c.close()关闭协程。
def produce(c): c.send(None) # 启动生成器 n = 0 while n < 5: n += 1 print('[PRODUCER] Producing %s...' % n) r = c.send(n) print('[PRODUCER] Consumer return %s' % r) c.close()c = consumer() # generatorproduce(c)2. 为什么要 close?
如果将 consumer 类比为一个 HTTP 连接池中的连接,那么 close() 的含义就是归还资源。
如果忘记关闭协程,可能会导致资源无法释放,从而造成连接泄露或内存泄露。
三、Python 中的上下文管理器(Context Manager)
1. 传统的资源管理方式
以打开文件为例,最原始的写法通常是:
try: f = open('/path/to/file', 'r') f.read()finally: if f: f.close()这种方式的问题在于:
- 样板代码较多
- 容易遗漏
close
2. 使用 with 语句简化资源管理
Python 提供了上下文管理器协议,通过 __enter__ 和 __exit__ 方法,将资源的获取与释放自动化:
with open('/path/to/file', 'r') as f: f.read()3. 自定义上下文管理器
下面是一个简单的 Query 类示例:
class Query(object): def __init__(self, name): self.name = name
def __enter__(self): print('Begin') return self
def __exit__(self, exc_type, exc_value, traceback): if exc_type: print('Error') else: print('End')
def query(self): print('Query info about %s...' % self.name)使用方式如下:
with Query('Bob') as q: q.query()四、使用协程简化上下文管理器的实现
手动实现 __enter__ 和 __exit__ 有时会显得比较繁琐。
Python 标准库 contextlib 提供了 @contextmanager 装饰器,可以基于生成器(协程)来实现上下文管理器。
1. 基于生成器的上下文管理器
核心思想是:
yield之前的代码 → 相当于__enter__yield之后的代码 → 相当于__exit__
from contextlib import contextmanager
class Query(object): def __init__(self, name): self.name = name
def query(self): print('Query info about %s...' % self.name)@contextmanagerdef create_query(name): print('Begin') q = Query(name) yield q print('End')使用方式完全一致:
with create_query('Bob') as q: q.query()这里的 create_query 本质上返回的是一个生成器对象,上下文管理器正是通过协程的“挂起与恢复”机制来完成资源的管理。
五、对任意代码块进行包装
基于同样的思路,我们也可以对任意代码块进行包裹,例如用于打标签、统计耗时、日志埋点等。
@contextmanagerdef tag(name): print("<%s>" % name) yield print("</%s>" % name)使用示例:
with tag("run"): print("running...")输出:
<run>running...</run>六、小结
- 协程的核心能力:代码可以被挂起,并从挂起点继续执行
- 生成器是协程的一种实现形式
send / yield / close构成了协程之间的通信机制- 上下文管理器本质上是对资源生命周期的管理
@contextmanager利用协程机制,大幅简化了上下文管理器的实现
如果你愿意,我也可以帮你把这篇文章改成面试版总结或博客发布版(含标题与结构目录)。