专项练习 · 9 分钟阅读

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 只能用于文件吗?

不是。任何实现上下文管理器协议的对象都可以使用,包括锁、数据库事务和测试夹具。

上下文管理器可以抑制异常吗?

可以,只要 __exit__ 返回真值。应谨慎使用,并把行为写进文档。

如果 __enter__ 抛出异常会怎样?

主体不会进入,__exit__ 也不会被调用。异常会传播给调用方。

练习上下文管理器

12 道题,覆盖 with、__enter__、__exit__、清理和 contextlib。

打开练习

继续阅读: