Python中的上下文管理器


非常好的问题 👍

contextmanager 是 Python 中一个非常强大的上下文管理器(Context Manager)工具,它让我们可以优雅地使用 with 语句来管理资源。

比如文件、数据库连接、锁、临时配置等。

下面我们一步步深入讲解 👇



🧩 一、什么是上下文管理器(Context Manager)

我们都用过 with open(...) as f:

with open('data.txt', 'r') as f:
    content = f.read()

等价于:

f = open('data.txt', 'r')
try:
    content = f.read()
finally:
    f.close()

with 会自动调用:

  • __enter__() 在进入代码块时执行;
  • __exit__() 在退出(包括异常)时执行。


🧱 二、用类实现上下文管理器(传统写法)

class MyResource:
    def __enter__(self):
        print("🔹 进入上下文")
        return self  # 返回值会赋给 as 后的变量

    def __exit__(self, exc_type, exc_val, exc_tb):
        print("🔹 退出上下文")
        if exc_type:
            print(f"⚠️ 捕获异常: {exc_val}")
        return False  # 返回 True 表示吞掉异常,False 则继续抛出

with MyResource() as r:
    print("🌟 执行中")

输出:

🔹 进入上下文
🌟 执行中
🔹 退出上下文


🧠 三、contextlib.contextmanager 装饰器(更简洁!)

Python 提供了标准库模块 contextlib,其中的 @contextmanager 装饰器

可以让我们用生成器函数来快速写出上下文管理器,而不用写类。

✨ 基本语法

from contextlib import contextmanager

@contextmanager
def my_context():
    print("🔹 进入上下文")
    yield "资源"  # yield 之前的代码相当于 __enter__
    print("🔹 退出上下文")  # yield 之后的代码相当于 __exit__

with my_context() as value:
    print("🌟 使用", value)

输出:

🔹 进入上下文
🌟 使用 资源
🔹 退出上下文


🧱 四、@contextmanager 的执行流程

等价于类版本:

阶段执行位置对应行为
进入 with 块前yield 之前资源获取、初始化
执行 withyield 返回值传递给 as
离开 with 块(包括异常)yield 之后清理、关闭资源
✅ 即使 with 块中抛出异常,yield 后的清理代码也会执行。


🧩 五、异常处理示例

from contextlib import contextmanager

@contextmanager
def managed_resource():
    print("打开资源")
    try:
        yield "resource"
    except Exception as e:
        print("捕获异常:", e)
    finally:
        print("关闭资源")

with managed_resource() as r:
    print("使用资源", r)
    raise ValueError("出错啦!")

输出:

打开资源
使用资源 resource
捕获异常: 出错啦!
关闭资源


⚙️ 六、真实场景示例

示例 1:临时修改工作目录

import os
from contextlib import contextmanager

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

with change_dir('/tmp'):
    print("当前目录:", os.getcwd())

print("恢复目录:", os.getcwd())


示例 2:数据库事务管理

@contextmanager
def transaction(conn):
    cursor = conn.cursor()
    try:
        yield cursor
        conn.commit()
    except:
        conn.rollback()
        raise
    finally:
        cursor.close()

使用时:

with transaction(conn) as cur:
    cur.execute("INSERT INTO users VALUES ('Tom')")


示例 3:临时禁用日志输出

import logging
from contextlib import contextmanager

@contextmanager
def silence_logging(level=logging.CRITICAL):
    old_level = logging.getLogger().level
    logging.getLogger().setLevel(level)
    try:
        yield
    finally:
        logging.getLogger().setLevel(old_level)


✅ 七、总结表

特性类实现@contextmanager 实现
写法需要定义 __enter__ / __exit__使用 yield 一次的函数
代码量简洁
用途更复杂的上下文对象简单场景、资源管理
异常处理手动实现自动捕获并传递异常上下文


🔥 八、小贴士

  • contextlib 还提供: contextlib.ExitStack → 管理多个上下文 contextlib.suppress → 忽略特定异常 contextlib.nullcontext → 占位上下文(什么都不做)

示例:

from contextlib import suppress

with suppress(FileNotFoundError):
    open('no_file.txt')


🎯 结论

@contextmanager 是写上下文管理器最优雅的方式。 它让“进入 / 退出资源”的逻辑自然地拆分在 yield 的两侧, 比写类简洁得多。


文章标签:

评论(0)