Skip to content

30|上下文管理器

文件、网络连接、数据库连接、锁——这些资源使用后都需要释放。手动调用 close()release() 容易遗漏,尤其在异常发生时。上下文管理器确保资源在使用完毕后被正确释放,无论正常结束还是异常退出。

一、with 语句

最熟悉的上下文管理器是文件操作:

python
with open("data.txt", "r") as f:
    content = f.read()
# 离开 with 块时,f.close() 自动调用

with 语句的执行流程:

  1. 调用 open("data.txt", "r") 得到文件对象
  2. 调用文件对象的 __enter__() 方法,返回值赋给 f
  3. 执行 with 块内的代码
  4. 调用文件对象的 __exit__() 方法,关闭文件

第 4 步在以下情况都会执行:正常结束、returnbreakcontinue、异常抛出。

二、自定义上下文管理器

实现 __enter____exit__ 两个方法:

python
class ManagedFile:
    def __init__(self, filename, mode):
        self.filename = filename
        self.mode = mode

    def __enter__(self):
        self.file = open(self.filename, self.mode)
        return self.file

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.file.close()
        return False   # 不吞掉异常,继续向上传播

使用:

python
with ManagedFile("data.txt", "r") as f:
    content = f.read()

__exit__ 的三个参数:异常类型、异常值、traceback。如果 with 块内没有异常,这三个参数都是 None。返回 True 表示吞掉异常,返回 False 表示继续传播。

三、contextlib 模块

用装饰器快速创建上下文管理器,不需要写类:

python
from contextlib import contextmanager

@contextmanager
def managed_file(filename, mode):
    f = open(filename, mode)
    try:
        yield f
    finally:
        f.close()

使用:

python
with managed_file("data.txt", "r") as f:
    content = f.read()

@contextmanager 装饰的函数逻辑:

  1. 执行到 yield 之前的代码(资源获取)
  2. yield 的值交给 with 语句的 as 变量
  3. 执行 with 块内的代码
  4. 回到生成器,执行 yield 之后的代码(资源释放)

四、实际应用:计时器

python
from contextlib import contextmanager
from datetime import datetime

@contextmanager
def timer(name):
    start = datetime.now()
    try:
        yield
    finally:
        elapsed = datetime.now() - start
        print(f"{name} 耗时: {elapsed.total_seconds():.3f}秒")


with timer("数据处理"):
    process_data()   # 假设这是一个耗时操作

五、实际应用:临时切换目录

python
from contextlib import contextmanager
import os

@contextmanager
def change_dir(path):
    old_dir = os.getcwd()
    os.chdir(path)
    try:
        yield
    finally:
        os.chdir(old_dir)


with change_dir("/tmp"):
    # 当前在 /tmp 目录
    with open("temp.txt", "w") as f:
        f.write("hello")
# 自动回到原来的目录

六、实际应用:锁

python
from threading import Lock
from contextlib import contextmanager

lock = Lock()

@contextmanager
def acquire_lock():
    lock.acquire()
    try:
        yield
    finally:
        lock.release()


with acquire_lock():
    # 临界区代码
    modify_shared_data()

七、contextlib 的其他工具

suppress

忽略指定异常:

python
from contextlib import suppress

with suppress(FileNotFoundError):
    os.remove("maybe_missing.txt")
# 文件不存在时不报错,也不抛异常

等价于:

python
try:
    os.remove("maybe_missing.txt")
except FileNotFoundError:
    pass

ExitStack

动态管理多个上下文:

python
from contextlib import ExitStack

with ExitStack() as stack:
    f1 = stack.enter_context(open("file1.txt"))
    f2 = stack.enter_context(open("file2.txt"))
    # 两个文件都会在 with 结束时关闭

记忆锚点with 语句确保资源释放;上下文管理器实现 __enter____exit__@contextmanager 用生成器快速创建;yield 前后分别是获取和释放逻辑;__exit__ 返回 True 吞掉异常,False 继续传播;contextlib.suppress 优雅忽略指定异常。