上下文管理器与装饰器类似,它们都是包装其他代码的工具。但装饰器用于包装定义的代码块(如函数或类),而上下文管理器可以包装任意格式的代码块。
有一些任务,可能事先需要设置,事后做清理工作。这种时候用上下文管理器就可以相当方便地进行处理。
例如,在对文件进行读写操作时,通常得这样进行
try:
file = open("/tmp/foo.txt")
data = file.read()
finally:
file.close()
这样的写法虽然没有问题,但总是需要手动关闭文件,很可能忘记关闭文件句柄,引起漏洞。
在python2.5后新增了关键字with,使用with语句即可进入上下文管理器,它可以在执行完代码之后自动帮我们关闭文件,因此上述代码可改写成:
with open('test.txt') as f:
data = f.read()
with究竟是如何工作的,或者说上面提到的上下文管理器究竟是什么?
with语句的作用实际上就是对其后的代码求值(本例中即为调用open函数)。该表达式返回一个对象,该对象包含两个特殊方法:__enter__()和__exit__()。
- 紧跟with后面的语句被求值后,返回对象的__enter__()方法被调用,这个方法的返回值将被赋值给as后面的变量。当with后面的代码块全部被执行完之后,将调用前面返回对象的__exit__()方法。
我们可以通过如下实例来看
class Sample:
def __enter__(self):
print("__enter__")
return "sample"
def __exit__(self, type, value, trace):
print("__exit__")
def get_sample():
return Sample()
with get_sample() as sample:
print(sample)
>>>
__enter__
sample
__exit__
整个过程有如下几步:
- __enter__()方法被执行
- __enter__()方法返回的值赋值给as后的变量sample
- 执行代码块
- __exit__()方法被调用
可以看出,with语句是进入上下文管理器的入口且默认执行__enter__函数,而退出上下文管理器时默认执行__exit__函数。
由上我们可以知道,打开和关闭资源(如文件和数据库连接)是编写上下文管理器的一个重要应用。但除此之外,上下文管理器还有另一个重要功能:
异常处理
with语句的表达式的作用是返回一个遵循特定协议的对象,该对象必须定义一个 __enter__方法和一个__exit__方法。
除了self参数,__enter__方法不接受任何参数,如果有as变量(as子句是可选项 ),返回值赋给as后的变量。
除了self参数,__exit__方法还带有三个位置参数:
- exc_type:一个异常类型
- exc_instance :一个异常实例
- traceback :一个回溯
无异常时它们全为None,但如果在代码块内有异常发生,则参数被填充
上下文管理器必须定义__exit__方法,该方法可以选择性地处理包装代码块中出现的异常,或者处理其他需要关闭上下文管理器状态的事情。如果exit方法接收一个异常,它可以
- 返回False实现异常的传播
- 返回True终止异常
- 抛出一个不同的异常,它将替代异常被发送出去