context manager

张开发
2026/6/9 13:16:23 15 分钟阅读
context manager
## 关于 Python 中的上下文管理器在 Python 里写代码有时候会遇到一些需要“善后”的场景。比如打开一个文件用完之后得记得关掉或者连接数据库操作完也得断开。这些操作如果忘了可能会出问题比如文件被占用、数据库连接数不够用之类的。上下文管理器就是用来处理这类问题的。它提供了一种结构化的方式来确保某些操作在执行前后能被正确处理。最典型的用法就是with语句。举个例子读写文件的时候一般会这么写withopen(data.txt,r)asf:contentf.read()这里open()返回的就是一个上下文管理器。在进入with块的时候文件被打开并赋值给f离开with块的时候无论中间有没有出错文件都会被自动关闭。这比手动写try...finally要简洁不少。它是怎么工作的上下文管理器背后其实有两个特殊方法__enter__和__exit__。__enter__在进入with块时调用返回值会赋给as后面的变量__exit__在离开with块时调用负责处理清理工作比如关文件、关连接、释放锁等等。自己实现一个上下文管理器也不难。比如模拟一个简单的“计时器”classTimer:def__enter__(self):importtime self.starttime.time()returnselfdef__exit__(self,exc_type,exc_val,exc_tb):importtime self.endtime.time()print(f耗时:{self.end-self.start:.2f}秒)withTimer()ast:# 模拟一些耗时操作importtime time.sleep(1)这样代码块执行的时间就会被自动打印出来。更轻便的写法如果觉得写一个类有点重也可以用contextlib模块里的contextmanager装饰器通过生成器来快速实现。上面的计时器可以改写成这样fromcontextlibimportcontextmanagerimporttimecontextmanagerdeftimer():starttime.time()yieldendtime.time()print(f耗时:{end-start:.2f}秒)withtimer():time.sleep(1)这种写法更函数式适合一些简单的场景。yield之前的部分相当于__enter__之后的部分相当于__exit__。实际用在哪里上下文管理器不只是用来开关文件。比如在多线程里管理锁可以确保锁一定会被释放importthreading lockthreading.Lock()withlock:# 临界区代码pass再比如临时修改某个配置执行完再改回来importsysfromcontextlibimportredirect_stdoutwithopen(output.log,w)asf,redirect_stdout(f):print(这行内容会被写入文件而不是打印到屏幕)这里还展示了同时使用多个上下文管理器用逗号隔开就行。一点细节__exit__方法会接收到三个参数分别代表异常类型、异常值和 traceback。如果代码块正常执行它们都是None。如果发生异常__exit__可以选择处理异常返回True或者让异常继续向上抛返回False。这让上下文管理器不仅能做清理还能做错误处理。比如可以写一个忽略特定错误的上下文管理器classIgnoreError:def__enter__(self):passdef__exit__(self,exc_type,exc_val,exc_tb):ifexc_typeValueError:print(忽略 ValueError)returnTruereturnFalsewithIgnoreError():raiseValueError(这个错误会被忽略)最后上下文管理器是 Python 里一个挺实用的特性。它把“准备”和“清理”的逻辑封装在一起让代码更清晰也更容易维护。平时写代码的时候如果遇到成对出现的操作比如打开关闭、加锁解锁、开始结束都可以考虑用上下文管理器来组织。时间长了代码会显得更有条理也不容易漏掉那些关键的清理步骤。

更多文章