Python 上下文管理器

with 语句配对资源获取与释放;实现 __enter__/__exit__ 或使用 contextlib。

#type / concept #status / growing #tech / dev #resource / python #tech / lang / python

[!info] 关联笔记

Python 上下文管理器

这个概念为什么出现

文件、锁、事务、临时目录必须“借出后归还”。只靠程序员记得 close() 会在异常路径泄漏。with 把获取/释放写成结构。

[!abstract] 一句话理解 上下文管理器实现获取与释放协议;with 保证离开块时调用清理逻辑,即使块内异常。

最小可运行示例

先把示例放进业务场景,再看代码:

场景:库存预留锁必须释放

# reserve_lock_cm.py
# 业务意图:预留库存时持有锁,结束或失败都释放。
# 教学点:
# - __enter__/__exit__;
# - 异常仍触发 exit;
# - exit 返回 False 传播异常。

class InventoryLock:
    def __init__(self, sku: str) -> None:
        self.sku = sku
        self.held = False

    def __enter__(self) -> "InventoryLock":
        self.held = True
        print(f"lock acquire {self.sku}")
        return self

    def __exit__(self, exc_type, exc, tb) -> bool:
        self.held = False
        print(f"lock release {self.sku}; exc={exc_type.__name__ if exc_type else None}")
        return False  # 不吞异常


def reserve(sku: str, ok: bool) -> None:
    with InventoryLock(sku) as lock:
        print("held?", lock.held)
        if not ok:
            raise RuntimeError("upstream fail")
        print("reserved")


def main() -> None:
    reserve("SKU-1", True)
    try:
        reserve("SKU-2", False)
    except RuntimeError as exc:
        print("caught:", exc)


if __name__ == "__main__":
    main()

建议运行:

python reserve_lock_cm.py

期望输出:

lock acquire SKU-1
held? True
reserved
lock release SKU-1; exc=None
lock acquire SKU-2
held? True
lock release SKU-2; exc=RuntimeError
caught: upstream fail

结合场景再看三个关注点

  1. 失败路径也释放锁——__exit__ 仍执行。
  2. 返回 False 让异常继续传播给调用方。
  3. 真实项目可用 threading.Lock/contextlib

核心概念与准确模型

with cm as x:
    ...
# 约等于 enter; try: body; finally: exit
  • contextlib.contextmanager:生成器写 CM
  • 多上下文:with A() as a, B() as b:
  • 异步:async with + __aenter__/__aexit__

边界情况与反直觉行为

  1. __exit__ 返回 True 会吞掉异常——极少需要。
  2. enter 失败不会调用 exit。
  3. 可重入/可重入锁语义需文档化。

常见误区

[!warning] 常见误区:with 只是语法糖可有可无 错误理解:finally 手写一样。
正确模型:协议化清理减少遗漏,是资源安全默认写法。

工程实践

  • 文件:with open(...)
  • 自研资源优先 CM
  • 测试断言异常后资源状态

本节总结

上下文管理器把生命周期变成语法结构。有获取就有释放,异常不是借口。

自测题

  1. 块内异常时 __exit__ 会执行吗?
  2. 何时用 contextlib.contextmanager
参考答案
  1. 会(只要 enter 成功)。
  2. 逻辑简单、想用生成器 yield 前后写获取/释放时。

延伸阅读与资料来源

资料类型支撑内容
with statement规范语法
contextlib文档工具
创建于 2026/7/15 更新于 2026/7/15