Python中的上下文管理器
已于 2025年01月20日 18:48 修改
访问次数:12
非常好的问题 👍
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 之前 | 资源获取、初始化 |
执行 with 块 | yield 返回值 | 传递给 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)