1. 什么是上下文管理器? #
1.1 什么是 with 语句? #
with 语句用于自动管理资源:进入时做"准备工作",退出时(无论正常结束还是异常)做"清理工作"。最常见的是打开文件:
# 用 with 打开文件,退出 with 块时自动关闭文件
with open("file.txt", "r", encoding="utf-8") as f:
content = f.read()
# 这里文件已经自动关闭,无需手动 f.close()不用 with 时,需要自己 try/finally 确保关闭,容易遗漏。
1.2 什么是上下文管理器? #
上下文管理器是实现了 __enter__ 和 __exit__ 方法的对象,可以和 with 配合使用。open() 返回的文件对象就是一个上下文管理器。
contextlib 的作用:不用手写 __enter__ 和 __exit__,用生成器或工具函数就能快速创建上下文管理器,代码更简洁。
通俗比喻:with 像"进门拿钥匙、出门还钥匙"的流程,上下文管理器就是定义"进门做什么、出门做什么"的规则,contextlib 帮你用更简单的方式写出这些规则。
2. 什么是 contextlib? #
contextlib 是 Python 标准库提供的模块,用于简化上下文管理器的编写,主要包含:
@contextmanager:用生成器定义上下文管理器,最常用closing():包装有close()方法的对象,退出时自动调用suppress():在with块内忽略指定异常
3. 用 @contextmanager 创建上下文管理器 #
3.1 基本用法 #
用 @contextmanager 装饰一个生成器函数,yield 之前的代码相当于 __enter__(进入时执行),yield 之后的代码相当于 __exit__(退出时执行)。
# 导入 contextmanager
from contextlib import contextmanager
# 用 @contextmanager 装饰生成器函数
@contextmanager
def my_context():
# yield 之前的代码:进入 with 时执行(相当于 __enter__)
print("进入 with,做准备工作")
try:
# yield 后面的值会赋给 as 后面的变量
yield "你好"
# yield 之后的代码:退出 with 时执行(相当于 __exit__)
finally:
# 用 finally 确保退出时一定会执行清理
print("退出 with,做清理工作")
# 使用
with my_context() as msg:
print("在 with 块内,收到的值:", msg)
print("正在使用资源...")
# 这里已经退出 with,清理已完成
print("程序继续")说明:yield 后面的 "你好" 会传给 as msg。无论 with 块内是否发生异常,finally 里的清理代码都会执行。
3.2 计时器 #
# 导入 contextmanager 和 time
from contextlib import contextmanager
import time
# 定义一个"计时"上下文管理器
@contextmanager
def timer():
# 进入时记录开始时间
start = time.perf_counter()
try:
yield
finally:
# 退出时计算并打印耗时
elapsed = time.perf_counter() - start
print(f"耗时: {elapsed:.3f} 秒")
# 使用:测量某段代码的执行时间
with timer():
time.sleep(1)
print("这段代码执行了约 1 秒")3.3 临时修改目录 #
# 导入 contextmanager、os 和 pathlib
from contextlib import contextmanager
import os
from pathlib import Path
# 定义一个"临时切换目录"的上下文管理器
@contextmanager
def cd(path):
# 进入时保存当前目录
old_dir = os.getcwd()
try:
# 切换到新目录
os.chdir(path)
yield
finally:
# 退出时恢复原目录
os.chdir(old_dir)
# 使用:在 with 块内切换到指定目录,退出后恢复
# 这里用当前目录下的子目录做演示(若不存在则创建)
demo_dir = Path("_contextlib_demo")
demo_dir.mkdir(exist_ok=True)
with cd(demo_dir):
print("当前目录:", os.getcwd())
# 在 with 块内,当前目录已切换
print("退出后目录:", os.getcwd())
# 清理演示目录
demo_dir.rmdir()4. closing:自动关闭有 close() 的对象 #
有些对象有 close() 方法,但不是上下文管理器(没有 __enter__/__exit__)。用 closing() 包装后,退出 with 时会自动调用 close()。
# 导入 contextlib
from contextlib import closing
# 模拟一个"有 close 方法"的对象(如数据库连接、网络连接等)
class SimpleResource:
def __init__(self):
print("资源已打开")
def close(self):
print("资源已关闭")
# 用 closing 包装,退出 with 时自动调用 close()
with closing(SimpleResource()) as res:
print("正在使用资源...")
print("with 已结束")说明:urlopen() 返回的对象、某些数据库连接等也有 close() 方法,同样可以用 closing() 包装,确保退出时自动关闭。
5. suppress:忽略指定异常 #
有时你希望某段代码忽略某些异常(比如文件不存在时跳过)。
5.1 不用 suppress 的写法 #
# 删除文件,如果不存在会报错
import os
try:
os.remove("不存在的文件.txt")
except FileNotFoundError:
pass # 忽略"文件不存在"错误5.2 用 suppress 的写法 #
# 导入 contextlib 和 os
from contextlib import suppress
import os
# suppress(FileNotFoundError) 表示忽略 FileNotFoundError
# 退出 with 时如果发生该异常,不会抛出,静默忽略
with suppress(FileNotFoundError):
os.remove("不存在的文件.txt")
# 程序正常继续,不会因为文件不存在而崩溃
print("程序继续执行")说明:可以同时忽略多种异常,如 suppress(FileNotFoundError, PermissionError)。
6. nullcontext:什么都不做的占位符 #
nullcontext 是一个"空"的上下文管理器,什么都不做,适合作为占位符。可以传入一个值,该值会通过 as 返回。
# 导入 contextlib
from contextlib import nullcontext
# 不传参数时,as 得到 None
with nullcontext() as x:
print("x =", x)
# 传参数时,as 得到该参数
with nullcontext(42) as value:
print("value =", value)说明:当某些情况下"不需要真正进入上下文"时,可以用 nullcontext() 占位,使代码结构统一。
7. 带锁的上下文管理器 #
结合 threading.Lock,可以封装一个"进入 with 时加锁、退出时解锁"的上下文管理器(其实 Lock 本身已经支持 with,这里仅作演示):
# 导入 contextlib 和 threading
from contextlib import contextmanager
import threading
# 创建一个锁
lock = threading.Lock()
# 用 @contextmanager 封装"加锁-解锁"逻辑
@contextmanager
def with_lock(lk):
# 进入时加锁
lk.acquire()
try:
yield
finally:
# 退出时解锁(无论是否异常)
lk.release()
# 使用
with with_lock(lock):
print("已获取锁,执行临界区代码")
# 模拟一些操作
print("已释放锁")说明:实际使用 threading.Lock 时,直接用 with lock: 即可,无需自己封装。这里只是演示 @contextmanager 的用法。
8. 注意事项 #
- 必须用 try/finally:
@contextmanager装饰的生成器中,yield之后的清理代码应放在finally里,确保异常时也会执行。 - yield 只能一次:生成器里只能
yield一次,yield之前的代码是"进入"逻辑,之后是"退出"逻辑。 - suppress 只忽略指定异常:其他异常仍会正常抛出。
9. 总结 #
| 内容 | 要点 |
|---|---|
| @contextmanager | 用生成器创建上下文管理器,yield 前是进入逻辑,后是退出逻辑 |
| closing() | 包装有 close() 的对象,退出 with 时自动调用 |
| suppress() | 在 with 块内忽略指定异常 |
| nullcontext() | 空占位符,可选返回一个值 |
记忆口诀:写上下文管理器用 @contextmanager,有 close 用 closing,要忽略异常用 suppress。