Appearance
30|上下文管理器
文件、网络连接、数据库连接、锁——这些资源使用后都需要释放。手动调用 close() 或 release() 容易遗漏,尤其在异常发生时。上下文管理器确保资源在使用完毕后被正确释放,无论正常结束还是异常退出。
一、with 语句
最熟悉的上下文管理器是文件操作:
python
with open("data.txt", "r") as f:
content = f.read()
# 离开 with 块时,f.close() 自动调用with 语句的执行流程:
- 调用
open("data.txt", "r")得到文件对象 - 调用文件对象的
__enter__()方法,返回值赋给f - 执行
with块内的代码 - 调用文件对象的
__exit__()方法,关闭文件
第 4 步在以下情况都会执行:正常结束、return、break、continue、异常抛出。
二、自定义上下文管理器
实现 __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 装饰的函数逻辑:
- 执行到
yield之前的代码(资源获取) yield的值交给with语句的as变量- 执行
with块内的代码 - 回到生成器,执行
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:
passExitStack
动态管理多个上下文:
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 优雅忽略指定异常。