Python 的 contextlib:把 try/finally 写成可复用的资源管理
写 Python 时,打开文件、拿数据库连接、临时改工作目录,这些操作都有一个共同模式:做事之前先准备,做完之后必须清理。最原始的写法是 try/finally,逻辑没错,但同样的清理代码散落在各处,改一处就得改一片。contextlib 就是用来把“准备—清理”这对操作打包成可复用结构的标准库。
最简单的形态是 contextmanager:把一个生成器变成上下文管理器,yield 之前是准备,yield 之后是清理,调用方只用一个 with 就能享受到完整的清理保证。注意 try/finally 仍然要写在生成器内部:yield 之后的代码只有放在 finally 里,才能保证调用方中途抛异常时也执行。如果只是把清理代码平铺在 yield 后面,异常一来就跳过了,资源照样泄漏。
一个更实用的例子是临时切换工作目录:很多脚本依赖相对路径,跑测试或调用外部工具前要临时切目录,完事再切回来。用上下文管理器封装后,调用处一目了然,离开 with 块工作目录自动回到原处,哪怕中间抛了异常。
几个容易忽略的细节:第一,清理代码本身也要防异常,如果 finally 里再抛异常,它会掩盖原始异常;清理步骤尽量只做尽力而为的事。第二,不要把上下文管理器当成函数调用:with working_directory(...) 和直接调用完全是两回事,后者只创建了一个生成器,里面的代码一行都不会执行。第三,多个资源可以叠加,清理按相反顺序执行;需要动态数量的资源时,用 contextlib.ExitStack 在循环里逐个压入,退出时统一按逆序清理。
什么时候不用它:如果清理逻辑只出现一次,try/finally 就够了。contextlib 的价值在于第二次出现:当同一对准备—清理在第二个地方冒头时抽出来复用,代码立刻变薄,遗漏清理的概率也随之下降。资源管理的本质不是少写几行代码,而是让“一定会被清理”这件事由结构来保证,而不是靠每个调用者的记性。
- 点赞
- 收藏
- 关注作者
评论(0)