@contextmanager可简化上下文管理器实现,但需用try/finally保证清理;closing()包装含close()的对象;suppress()忽略指定异常;ExitStack用于动态嵌套上下文。

用 @contextmanager 替代手动写 __enter__ 和 __exit__
手动实现上下文管理器要写两个方法,容易漏掉异常处理逻辑或资源清理顺序。用 @contextmanager 装饰器,只需一个生成器函数,yield 之前的部分相当于 __enter__,之后相当于 __exit__。
常见错误是忘了加 try/finally —— @contextmanager 不自动保证清理,必须显式包裹。
-
yield前做资源获取(如打开文件、连接数据库) -
yield后必须用finally或直接放在yield同级缩进下做释放(否则异常时不会执行) - 如果想捕获并吞掉异常,可在
except块里返回True,和__exit__行为一致
from contextlib import contextmanager <p>@contextmanager def open_file(name): f = open(name, 'w') try: yield f finally: f.close() # 即使上面抛异常,这里也执行 </p>
用 contextlib.closing() 包裹已有 close 方法的对象
有些对象有 close() 但没实现上下文协议(比如某些老库的 socket、response 对象),直接 with 会报 AttributeError: __exit__。
closing() 是最轻量的包装方式,它只调用一次 close(),不干涉其他行为。
- 仅适用于有
close()方法且无参数的对象 - 不能替代需要复杂清理逻辑的上下文管理器(比如要先 flush 再 close)
- 注意:它不会抑制异常,异常仍会向上冒泡
from contextlib import closing
import urllib.request
<p>with closing(urllib.request.urlopen('<a href="https://www.php.cn/link/563dc6cc0586f6fe22c71fac9b8783ae">https://www.php.cn/link/563dc6cc0586f6fe22c71fac9b8783ae</a>')) as resp:
data = resp.read() # resp 自动关闭,无需 resp.close()
</p>
用 contextlib.suppress() 忽略特定异常
不是所有“忽略异常”都要写 try/except: pass。当目标明确是“如果某异常发生就当没发生”,suppress() 更语义清晰、代码更短。
典型场景:删除临时文件、关闭可能已断开的连接、清理可能不存在的资源。
- 传入多个异常类可同时抑制(如
suppress(FileNotFoundError, PermissionError)) - 它只抑制进入
with块后发生的匹配异常,不捕获with表达式本身(如open()失败)的异常 - 被抑制的异常不会留下痕迹,无法记录或重抛 —— 如果需要日志,还是得手写
try/except
from contextlib import suppress
import os
<p>with suppress(FileNotFoundError):
os.remove('temp.log') # 文件不存在也不报错
</p>
嵌套多个上下文时优先用 contextlib.ExitStack
当需要动态决定进几个上下文(比如根据配置开关日志、监控、事务),或上下文数量不确定时,硬写嵌套 with 既难读又难维护。
ExitStack 允许运行时注册任意多个上下文,并统一管理退出逻辑,还支持在中途取消已进入的上下文。
-
enter_context()返回值就是上下文管理器的__enter__返回值 -
push()可注册普通回调(类似__exit__的清理函数),不依赖上下文协议 - 若某个
enter_context()失败,已成功进入的会按逆序自动退出 —— 这点比手写try/finally链可靠得多
from contextlib import ExitStack
<p>with ExitStack() as stack:
file1 = stack.enter_context(open('a.txt', 'w'))
file2 = stack.enter_context(open('b.txt', 'w'))
if need_log:
log_file = stack.enter_context(open('log.txt', 'a'))</p><h1>所有文件在退出时自动关闭</h1><p></p>
真正容易被忽略的是异常传播路径:@contextmanager 里 yield 后的代码若出错,会覆盖原始异常;ExitStack 中某个 enter_context() 报错,其余已进的上下文虽能清理,但原始异常仍是那个最先失败的——这些细节不看源码或不测异常分支根本意识不到。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











