进阶篇 · Advanced
lv.3 进阶
kp-025
上下文管理器(with)
1. 一句话定义
with 表达"进入 → 使用 → 无论成败都清理"的确定性资源管理:对象实现 __enter__/__exit__ 即可被 with 使用;临时行为可用 contextlib 三行搞定。
2. 为什么重要
try/finally 的结构化替代:文件、锁、数据库连接、临时目录、计时段——一切"必须成对出现的操作"都该长成 with。Python 3.12 的单文件 __init__ 生成器让这类代码更省。
3. 前置知识
kp-016(with open 的表层用法)、kp-019(协议)。
4. 核心概念
python
# ① 协议类
class Timer:
def __enter__(self):
self.t0 = time.perf_counter()
return self # as 变量拿到的是 __enter__ 的返回值
def __exit__(self, exc_type, exc, tb):
print(f"耗时 {time.perf_counter()-self.t0:.3f}s")
return False # False/None:异常继续传播;True:吞掉
with Timer():
heavy()
# ② contextlib:临时上下文的最小成本写法
from contextlib import contextmanager
@contextmanager
def timer():
t0 = time.perf_counter()
try:
yield # yield 之前 = __enter__,之后 = __exit__
finally:
print(f"耗时 {time.perf_counter()-t0:.3f}s")
# ③ 标准库常用工具
import contextlib, tempfile
with contextlib.suppress(FileNotFoundError): # 静默特定异常
os.remove("tmp.txt")
with open("a") as fa, open("b") as fb: ... # 多资源,3.1+ 可写括号组
with contextlib.ExitStack() as stack: # 动态数量的资源
files = [stack.enter_context(open(p)) for p in paths]5. 原理与机制
text
with obj as x:
body
等价于:
mgr = obj
x = mgr.__enter__()
try:
body
except BaseException as e:
if not mgr.__exit__(type(e), e, e.__traceback__): raise
else:
mgr.__exit__(None, None, None)__exit__ 返回真值即"已处理"(吞异常)——能力强大但慎用,默认 return False。
6. 关键事实(模型/图示)
text
进入 with ──► __enter__(可返回值给 as)
退出 with ──► __exit__ 必被调用(正常/异常都算"退出")
生成器版:yield 前后的代码天然对应 enter/exit7. 直观类比
with 是手术室的进出规程:进门(enter)自动更衣消毒,出门(exit)无论手术成功失败都自动脱衣销毁器械——规程(协议)保证"进去过的人一定按流程出来",没有"忘了"这个状态。
8. 实例与案例
python
# 数据库事务模板
@contextmanager
def transaction(conn):
try:
yield conn
conn.commit()
except Exception:
conn.rollback()
raise # 回滚后继续抛出,调用方知情9. 常见误区
__exit__里吞异常忘返回值 —— 返回 None 等于 False(不吞);想要"处理且吞掉"必须显式return True。- 以为 with 能管理任意对象 —— 没实现协议的对象进 with 直接
AttributeError;临时写法用 contextlib。 - 在
__exit__里再抛新异常 —— 会与原异常链交织,极难排查;清理代码应只清理。 - 嵌套深了还手动 with 缩进 —— 资源数量动态时用
ExitStack。
10. 自测题
with obj as x:中 x 是什么?谁把它赋给 x?@contextmanager装饰的生成器里,yield 前后各对应什么?抛出异常会走哪段?- 什么时候必须用
ExitStack而不是多个 with?
参考答案
__enter__的返回值;with 语句机制自动绑定。- yield 前 = 进入逻辑,yield 后 = 退出逻辑;进入与使用中抛出的异常都会在 yield 处重新抛出,落入 finally/except 段。
- 资源集合在运行期才确定(列表里有多少就开多少个)时。
11. 与其他知识点的关系
- kp-015 异常处理:with 是"清理"维度的专用语法。
- kp-024 装饰器:同为"包裹执行",一个作用于定义期、一个作用于调用期。
- kp-031 asyncio:异步上下文
async with的同步版原理。
12. 延伸阅读
- PEP 343(with 语句)
- contextlib 文档:https://docs.python.org/zh-cn/3/library/contextlib.html