上下文管理器允许自动化和控制代码块的执行,确保资源的最终化——例如,自动关闭文件、释放数据库中的事务管理器或锁定/解锁线程。在Python的标准中,引入了这一范式,以便安全和方便地管理外部资源,无需手动跟踪工作结束的最终阶段。
历史背景:
在引入上下文管理器之前,需要显式调用close()/release()方法来释放资源,这会导致在发生异常时出现错误。随着with ... as ...:结构的出现,Python允许明确地定义“资源的作用域”,自动调用初始化和结束工作的相关方法。
问题:
手动管理资源的“关闭”是危险的——如果忘记调用close()(或release()),资源将一直占用,直到进程结束,甚至可能永远挂起。在处理文件、网络连接、数据库事务时,尤其严重。
解决方案:
上下文管理器通过魔法方法__enter__()和__exit__()实现。当进入with块时,Python调用__enter__,当退出时调用__exit__,即使在块内部发生了异常。
代码示例:
class ManagedFile: def __init__(self, filename): self.filename = filename self.file = None def __enter__(self): self.file = open(self.filename, 'w') return self.file def __exit__(self, exc_type, exc_val, exc_tb): if self.file: self.file.close() # 可以通过return True来抑制异常,通常使用return False with ManagedFile('demo.txt') as f: f.write('Hello, world! ') # 文件得到保证关闭
关键特性:
是否可以在不同的with中连续使用同一个上下文管理器实例?
不可以:通常资源在__enter__中打开,在__exit__中释放,对象变得不适合重复使用。最好为每个with块创建一个新对象。
如果需要在一个with中使用多个资源该怎么办?
可以通过逗号分隔变量:
with open('a.txt') as fa, open('b.txt') as fb: ...
或者在复杂情况下使用contextlib.ExitStack。
作为具有__enter__/__exit__的类和作为带有@contextmanager装饰器的生成器编写上下文管理器有什么区别?
contextlib模块的@contextmanager装饰器允许通过yield更简单地实现管理器:
from contextlib import contextmanager @contextmanager def open_file(name): f = open(name) try: yield f finally: f.close() with open_file('file.txt') as f: ...
开发者通过open()打开文件,写入数据,但忘记调用close()——文件可能保持打开状态(特别是在发生错误时),数据未被写入。
优点:
缺点:
处处使用with open()作为标准(或自定义管理资源),上下文管理器正确实现了资源的释放。
优点:
缺点: