上下文管理器

在 Python 中,上下文管理器是一种对象,它定义了程序在进入和退出某个运行环境时的行为。

简单说:

“上下文管理器可以在一段代码执行前后自动执行特定操作。”

最常见的例子:

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

这段代码的行为是:

  1. 进入上下文:调用 open() → 创建文件对象。
  2. 执行代码块with 缩进内的语句。
  3. 退出上下文:自动调用 f.close(),即使中途发生异常也会关闭文件。

工作原理

语法结构

1
2
with 表达式 [as 变量]:
代码块

工作流程

  1. 执行 表达式,获取一个上下文管理器对象。

  2. 调用该对象的 __enter__() 方法。

    • 如果有 as 变量,将 __enter__() 的返回值赋给 变量
  3. 执行 with 代码块中的语句。

  4. 当代码块执行完毕(无论是否发生异常):

    • 调用对象的 __exit__(exc_type, exc_value, traceback)
    • __exit__ 决定是否“吞掉”异常(返回 True 表示忽略异常,False 表示继续抛出)。

常见示例

传统写法:

1
2
3
4
5
f = open('data.txt', 'r')
try:
content = f.read()
finally:
f.close()

with 写法:

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

✅ 优点:

  • 自动关闭文件。
  • 即使读取中发生异常,也能安全释放资源。
  • 更简洁、更安全。

核心机制

一个对象要能用于 with,必须实现这两个方法:

方法 调用时机 作用
__enter__(self) 进入 with 块前调用 返回要绑定的对象
__exit__(self, exc_type, exc_val, exc_tb) 离开 with 块时调用 清理操作

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class MyContext:
def __enter__(self):
print("进入上下文")
return "Hello"

def __exit__(self, exc_type, exc_value, traceback):
print("退出上下文")
if exc_type:
print(f"捕获异常: {exc_value}")
print("清理完成")
# 返回 True 表示吞掉异常
return True

with MyContext() as msg:
print("执行中:", msg)
raise ValueError("测试异常")

输出:

1
2
3
4
进入上下文
执行中: Hello
捕获异常: 测试异常
清理完成

注意:异常被吞掉了,没有抛出

多个上下文并列写法,Python 3.1+ 加入此方法

1
2
with open('a.txt') as fa, open('b.txt') as fb:
data = fa.read() + fb.read()

与嵌套写法等价:

1
2
3
with open('a.txt') as fa:
with open('b.txt') as fb:
data = fa.read() + fb.read()

应用场景

场景 示例 自动管理内容
文件读写 with open(...) as f: 文件句柄关闭
线程锁 with lock: 加锁 / 解锁
数据库事务 with connection: 提交或回滚
网络连接 with socket: 关闭连接
临时修改状态 with decimal.localcontext(): 精度恢复
压缩包 / tarfile with zipfile.ZipFile() as z: 文件关闭

contextlib 模块

装饰器 @contextmanager

如果你不想定义类、实现 __enter__/__exit__
Python 提供了 contextlib.contextmanager 装饰器,可以让你用函数快速创建上下文管理器。

示例:

1
2
3
4
5
6
7
8
9
10
from contextlib import contextmanager

@contextmanager
def managed_resource():
print("获取资源")
yield "资源对象"
print("释放资源")

with managed_resource() as r:
print("使用资源:", r)

输出:

1
2
3
获取资源
使用资源: 资源对象
释放资源

✅ 注意:

  • yield 之前的代码相当于 __enter__
  • yield 之后的代码相当于 __exit__
  • 如果 with 内部发生异常,会在 yield 处重新抛出,可在 try...finally 中清理。

装饰器 @contextmanager 处理异常

1
2
3
4
5
6
7
8
9
10
11
12
13
@contextmanager
def managed():
print("进入")
try:
yield
except Exception as e:
print("捕获异常:", e)
finally:
print("退出")

with managed():
print("执行中")
raise ValueError("出错")

输出:

1
2
3
4
进入
执行中
捕获异常: 出错
退出

contextlib 常用辅助工具

工具 说明 示例
contextmanager 函数快速创建上下文 上例
closing() 确保对象有 .close() 方法时自动关闭 with closing(urlopen(url)) as page:
suppress() 忽略特定异常 with suppress(FileNotFoundError): os.remove('tmp.txt')
ExitStack 动态管理多个上下文 动态打开多个文件

示例:suppress

1
2
3
4
5
6
from contextlib import suppress
import os

with suppress(FileNotFoundError):
os.remove('nonexistent.txt')
# 不会抛出异常

示例:ExitStack

当需要动态创建多个上下文(数量在运行时才确定):

1
2
3
4
5
6
7
8
from contextlib import ExitStack

files = ['a.txt', 'b.txt', 'c.txt']
with ExitStack() as stack:
handles = [stack.enter_context(open(fname, 'w')) for fname in files]
for h in handles:
h.write("Hello\n")
# 所有文件都会自动关闭

最佳实践

  1. 实现 __enter__ / __exit__,或者使用 @contextmanager
  2. 始终在 __exit__ / finally 中释放资源。
  3. 若资源必须关闭,最好配合异常处理,避免资源泄漏。
  4. 返回值尽量简单,常为自身或资源对象。
  5. 如果要吞掉异常,请确保你真的希望忽略它(一般返回 False)。

思维导图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
with 上下文管理器

├── 文件管理
│ └── open('file')

├── 并发控制
│ └── threading.Lock()

├── 数据库连接
│ └── with connection:

├── 自定义
│ ├── __enter__ / __exit__
│ └── @contextmanager + yield

├── contextlib 工具
│ ├── closing()
│ ├── suppress()
│ ├── ExitStack()
│ └── redirect_stdout()

└── 优点
├── 自动清理资源
├── 异常安全
└── 语法简洁,可读性强