Python 上下文管理器练习指南
成功路径上的清理很容易忘记,异常打断函数时更容易漏掉。上下文管理器把准备和收尾放在代码块两侧,让清理逻辑只有一个明确的归属。
用 with 成对操作
with 语句打开资源、执行代码块,并在结束时关闭资源。文件是最典型的例子,但同样的模式也适用于锁、临时目录和事务。代码块应专注于真正需要该资源的工作。
from pathlib import Path
path = Path("pypractice-guide.txt")
path.write_text("hello\n", encoding="utf-8")
with path.open(encoding="utf-8") as handle:
print(handle.read().strip())
path.unlink()受管理的文件
实现上下文管理器协议
__enter__ 准备资源,并返回绑定到 as 的值。__exit__ 接收异常类型、异常值和回溯信息,然后执行清理。返回假值会让异常继续传播;返回真值会抑制异常。
class Guard:
def __enter__(self):
print("enter")
return self
def __exit__(self, exc_type, exc, tb):
print("exit")
with Guard():
print("inside")准备与收尾
出错时清理仍会运行
代码块不必为每个资源操作都套一层 try/finally。即使主体抛出异常,__exit__ 也会在异常继续传播前被调用。这正是 with 对正确性的价值,而不只是写法更短。
class Guard:
def __enter__(self):
return self
def __exit__(self, *args):
print("cleaned")
try:
with Guard():
raise ValueError("boom")
except ValueError:
print("caught")异常逃出前先清理
用 contextmanager 构造
用 contextlib.contextmanager 装饰生成器。yield 之前是准备,yield 之后是清理,yield 出来的值可以通过 as 获取。保留且只保留一个 yield,并把清理放在它后面。
from contextlib import contextmanager
@contextmanager
def label(name):
print("start", name)
yield name
print("end", name)
with label("demo") as value:
print("body", value)基于生成器的管理器
不要意外吞掉异常
从 __exit__ 返回 True 会抑制异常。对明确的策略可能有帮助,但也可能掩盖错误。调用方应该看到错误时,返回 None 或 False。任何抑制行为都要写清楚。
class Visible:
def __enter__(self):
return self
def __exit__(self, *args):
return False
with Visible():
print("still visible")让错误继续传播
你的练习清单
- 准备和清理属于一起时使用 with。
- 从 __enter__ 返回真正有用的对象。
- 把清理放在 __exit__ 中,而不是只处理成功路径。
- 小型生成器管理器可以用 contextlib.contextmanager。
- 除非有意抑制,否则返回 False 或 None。
常见问题
with 只能用于文件吗?
不是。任何实现上下文管理器协议的对象都可以使用,包括锁、数据库事务和测试夹具。
上下文管理器可以抑制异常吗?
可以,只要 __exit__ 返回真值。应谨慎使用,并把行为写进文档。
如果 __enter__ 抛出异常会怎样?
主体不会进入,__exit__ 也不会被调用。异常会传播给调用方。
练习上下文管理器
12 道题,覆盖 with、__enter__、__exit__、清理和 contextlib。
继续阅读: