← [ BACK_TO_NOTES ]

Python 上下文管理器

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

#concept #growing #dev #python #python

[!info] related notes

Python 上下文管理器

这个概念为什么出现

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

[!abstract] 一句话理解 __enter__ 成功后,with 在块正常结束、return/break 或异常退出时调用 __exit__。是否释放资源或抑制异常取决于实现;进程强制终止等情况不能靠它保证清理。

最小可运行示例

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

场景:模拟库存锁的进入与退出协议

本例的 held 只是布尔标志,用于观察协议顺序,不提供线程、进程或数据库互斥;真正的库存并发控制需用共享锁或事务。

# 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 成功后进入 body;退出时把异常信息交给 exit,exit 可决定抑制异常。
  • contextlib.contextmanager 要求生成器恰好 yield 一次;获取放 yield 前,释放放 try/finally,块内异常会在 yield 位置重新抛入。捕获后未重新抛出可能抑制该异常。
  • 多上下文从左到右进入、反向退出;B 进入失败时,已经成功进入的 A 仍会退出。
  • 异步:async with + __aenter__/__aexit__

边界情况与反直觉行为

  1. __exit__ 返回真值会抑制块内异常;返回假值/None 则传播。若清理自身抛出新异常,传播的异常会改变,原异常通常保留为上下文。
  2. __enter__ 失败时不会调用该管理器的 __exit__,进入过程已取得的资源需自行清理。
  3. 可复用与可重入不同:顺序再次使用不代表可以嵌套进入同一个对象;contextmanager 产生的单个管理器通常只能进入一次。

常见误区

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

工程实践

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

本节总结

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

自测题

  1. 块内异常时 __exit__ 会执行吗?
  2. 何时用 contextlib.contextmanager?
参考答案
  1. 正常 Python 控制流中,只要 enter 成功就会调用 exit;仍需实现正确清理,不能覆盖强制终止进程等情况。
  2. 逻辑简单、想用生成器 yield 前后写获取/释放时。

延伸阅读与资料来源

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

键盘导航

j / k / ↑ / ↓
选择对象
Enter / o
打开选择
/ 或 Ctrl+K
全站搜索
i / f / 1–4
筛选当前列表
g → h / l / p / t / a
首页 / 笔记 / 项目 / 工具 / About
r → 1–9
打开所选对象的关联
u
返回上一页
Esc
关闭浮层 / 取消命令;输入框内清空文本