人人都会AI编程

11.4 上下文管理器

更新时间:2026-07-12

上下文管理器是 Python 管理资源的一种优雅机制。文件处理、数据库连接、线程锁——这些资源用完后必须正确释放,否则可能导致文件被锁定、连接泄漏、死锁等问题。上下文管理器就是为解决这个痛点设计的,核心是 with 语句。

为什么需要 with 语句?

在没有 with 的时候,我们通常这样操作文件:

f = open('data.txt', 'r')
try:
    content = f.read()
finally:
    f.close()  # 无论如何都要关闭文件

每次都要写 try...finally,琐碎且容易忘记。用 with 之后,代码简洁且绝对安全:

with open('data.txt', 'r') as f:
    content = f.read()
# 出 with 块后,文件自动关闭,即使发生异常也会关闭

with 语句的执行流程

with 其实是一套固定协议的语法糖:

  1. 对表达式求值,获得一个上下文管理器对象
  2. 调用该对象的 enter() 方法,返回值通常赋给 as 后面的变量。
  3. 执行 with 内部的代码块。
  4. 无论代码块正常结束还是抛出异常,都会调用上下文管理器的 exit() 方法,完成清理工作。

自定义上下文管理器(类实现)

任何一个实现了 enterexit 方法的类,都可以作为上下文管理器。

class MyFile:
    def __init__(self, filename, mode):
        self.filename = filename
        self.mode = mode
        self.file = None
    
    def __enter__(self):
        self.file = open(self.filename, self.mode)
        return self.file  # 作为 as 后面的对象
    
    def __exit__(self, exc_type, exc_val, exc_tb):
        if self.file:
            self.file.close()
        # 返回 False 会让异常继续传播,True 则抑制异常

with MyFile('test.txt', 'w') as f:
    f.write('hello')

exit 的三个参数分别是异常类型、异常值、回溯对象。如果执行过程中没有异常,它们都是 None。如果返回 True,异常被吞掉(通常不推荐,除非有明确的理由)。

使用 contextlib 简化

contextlib.contextmanager 装饰器可以将一个生成器函数转化为上下文管理器,代码量更少:

from contextlib import contextmanager

@contextmanager
def my_open(filename, mode):
    f = open(filename, mode)
    try:
        yield f           # 把资源交出给 with 块
    finally:
        f.close()

with my_open('test.txt', 'r') as f:
    print(f.read())

yield 之前的代码相当于 enter 的逻辑,yield 之后(在 finally 中)相当于 exit 的清理逻辑。这种方式更直观,适合大多数自定义上下文管理器的场景。

常见应用场景

  • 文件处理open() 是最典型的上下文管理器。
  • 线程锁threading.Lock 可以 with lock: 自动获取和释放锁,避免死锁。
  • 数据库连接与事务:很多数据库驱动支持 with 管理连接、游标或事务,比如 with conn.cursor() as cursor:
  • 临时修改状态:例如 with redirect_stdout(io.StringIO()) as buf: 暂时将标准输出重定向到字符串缓冲区,处理完毕自动恢复。
  • 资源并发控制:比如临时设置日志级别、临时修改环境变量等,在 with 块内生效,完事后自动还原。

实际开发中的优势

  • 防泄漏:释放资源的代码由上下文管理器保证执行,开发者不会因为忘记在分支、异常中写清理逻辑而出错。
  • 代码可读性:资源获取和释放的边界用缩进清楚呈现,一眼就能看出资源的有效范围。
  • 组合方便:可以嵌套多个 with,或者使用 with contextlib.ExitStack() 动态管理多个上下文管理器,特别适合需要不同运行时环境的复杂场景。

理解了上下文管理器,就能把很多“获取-使用-释放”的模式变成一行 with,代码可靠性直接上一个台阶。