Python上下文管理器(Context managers)
上下文管理器
在 with 语句中使用,通过定义 __enter__() 和 __exit__() 方法来控制环境状态的对象。
上下文管理器允许你在有需要的时候,精确地分配和释放资源。上下文管理器是指在一段代码执行之前执行一段代码,用于一些预处理工作;执行之后再执行一段代码,用于一些清理工作。比如打开文件进行读写,读写完之后需要将文件关闭。在上下文管理协议中,有两个方法__enter__和__exit__,分别实现上述两个功能。比如在数据库操作中,操作之前需要连接数据库,操作之后需要关闭数据库。
讲到上下文管理器最广泛的案例就是with语句了。with语法。基本语法格式为:
with EXPR [as VAR]:
BLOCK
其中的EXPR是上下文表达式(Context Expression),该表达式要返回一个上下文管理器对象。返回的是一个对象,var用来保存EXPR表达式返回的对象,可以有单个或者多个返回值。这里的as VAR可以省略,如果指定了 as 子句的话,会将上下文管理器的 __enter__() 方法的返回值赋值给VAR。VAR可以是单个变量,或者由“()”括起来的元组(不能是仅仅由“,”分隔的变量列表,必须加“()”)。
代码块BLOCK, with 语句包裹起来的代码块,在执行语句体之前会调用上下文管理器的 __enter__() 方法,执行完语句体之后会执行 __exit__() 方法。在这里VAR可以当做普通变量使用。
以打开一个文件hello.txt并写入 hello, world 为例
我们先对比如下两种方式:
方式一,代码如下:
f = open("d:/hello.txt", 'w')
try:
f.write('hello, world')
finally:
f.close()
【若d盘中已有hello.txt文件将被替换掉,没有则新建hello.txt文件,双击它,可以看到其中的内容】
方式二,代码如下:
with语句通过在上下文管理器中封装try…finally语句的标准用法来简化异常处理。
with open('d:/hello.txt', 'w') as f:
f.write('hello, world')
两种方式代码是等价的,方式二使⽤了with,会自动关闭已打开的⽂件,如果在往⽂件写数据时发⽣异常,它也会尝试去关闭⽂件。
【Python 对一些内建对象进行改进,加入了对上下文管理器的支持,可以用于 with 语句中,比如可以自动关闭文件、资源的加锁和解锁等。】
如何来实现我们自己的上下文管理器
下面介绍如何来实现我们自己的上下文管理器?或者说,如何使自定义的对象支持with?
自定义对象实现上下文管理器即可。有两种方式:基于类实现方式和基于生成器实现方式。
上下文管理器是一个简单的协议(接口),自定义的对象需要遵循这个协议(接口)来支持with语句。具体做法,向自定义对象中添加__enter__和__exit__方法,Python将在资源管理的适当时间调用这两种方法。
还是用前面提到的 打开一个文件hello.txt并写入 hello, world 例子,
☆基于类实现方式
#先定义类
class ManagedFile:
def __init__(self, name):
= name
def __enter__(self):
self.file = open(, 'w')
return self.file
def __exit__(self, exc_type, exc_val, exc_tb):
if self.file:
self.file.close()
#提示ManagedFile类遵循上下文管理器协议,与原open()一样也支持with语句
with ManagedFile('d:/hello.txt') as f:
f.write('hello, world')
进入with语句上下文,Python调用__enter__获取资源,离开with语句上下文,Python调用__exit__释放资源。
☆基于生成器实现
利用标准库contextlib模块的contextmanager装饰器重写之前的ManagedFile上下文管理器,代码如下:
from contextlib import contextmanager
@contextmanager
def managed_file(name):
try:
f = open(name, 'w')
yield f
finally:
f.close()
#提示managed_file遵循上下文管理器协议
with managed_file('d:/hello.txt') as f:
f.write('hello, world')
这个⽅法使用了⽣成器(使用了yield)和装饰器(使用了@函数名)的⼀些知识。