一、上下文管理器
上下文管理器是对Context Manager的翻译 ,上下文是 context 直译的叫法,在程序中用来表示代码执行过程中所处的前后环境.
在文件操作时,需要打开、关闭文件,而在文件在进行读写操作时,就是处在文件操作的上下文中,也就是文件操作环境中.
语法: with
with 语句是 Pyhton 提供的一种简化语法,从Python 2.5 开始引入的一种与异常处理相关的功能,适用于对资源进行访问的场合,确保不管使用过程中是否发生异常都会执行必要的“扫尾”操作,释放资源.
比如文件使用后自动关闭、数据库连接的自动关闭等.
二、with上下文管理器示例
代码语言:javascript复制with open('test.txt', 'a') as f:
f.write('上下文管理n')
运行以上代码,会在代码同级目录下创建一个叫test.txt的文本文件,并在文件中追加内容"上下文管理",并换行.
在执行 with 语句时,首先执行 with 后面的 open 代码(如果已经有test.txt文件则打开,如果没有test.txt文件则创建),然后通过as将代码的结果保存到 f 中(相当于给test.txt起个别名f,f只是一个变量名,您可以自定义)
在with下面的代码块是对test.txt执行的操作,如示例中的操作是写入内容.
在操作后,不需要对文件test.txt进行关闭操作f.close(),with上下文管理器会在文件使用完后帮我们关闭test.txt文件.
这么做即可以简化代码,又可以避免因粗心忘记执行关闭操作而出现异常,因为在实际开发中,打开一个文件后,进行的操作可能非常复杂,这种情况是很可能忘记做"扫尾"操作的.
三、with上下文管理的原理
在使用with上下文管理器时,并不是不需要关闭文件,而是文件的关闭操作在 with 的上下文管理器中已经实现了.当文件操作执行完成后,with语句会自动调用上下文管理器里的关闭语句来关闭文件资源.
但是,我们的应用场景并不是一成必变,with是怎么实现帮我们做"扫尾"操作的呢?
with语句在执行时,调用上下文管理器中的 __enter__ 和 __exit__ 两个方法,这两个方法就是上下文管理器中实现的方法.
__enter__ 方法会在执行with后面的语句时执行,一般用来处理操作前的内容.比如打开文件,创建对象,初始化等.
__exit__ 方法会在with内的代码执行完毕后执行,一般用来处理一些善后收尾工作,比如文件的关闭,数据库的关闭等.
四、自定义上下文管理器
根据上下文管理的原理,上下文管理器的原理是实现了__enter__和__exit__这两个方法,所以我们可以根据此原理来自定义自己的上下文管理器.
在自定义上下文管理器时,只要在类中实现 __enter__ 和 __exit__ 两个方法即可.
代码语言:javascript复制class OpenFile(object):
"""自定义上下文管理类"""
def __init__(self, file, mode):
self._file = file
self._mode = mode
def __enter__(self):
print('__enter__ 打开文件')
self._handle = open(self._file, self._mode)
return self._handle
def __exit__(self, exc_type, exc_val, exc_tb):
print('__exit__ 关闭文件')
self._handle.close()
with OpenFile('test01.txt', 'w') as f:
f.write('自定义上下文管理n')
上面的代码中,我们不是使用Python实现好的open来打开文件,而是通过自定义的OpenFile类来完成文件的操作.
代码运行过程和结果与用open类似,会先执行OpenFile后的代码(自定义类OpenFile中的代码),如果存在文件test01.txt,打开文件并在其中写入"自定义上下文管理",如果不存在文件test01.txt,则创建文件并写入"自定义上下文管理".
说明我们成功实现了自定义上下文管理.
五、上下文管理中的自定义异常处理
我们看到,__exit__ 方法中有三个参数exc_type ,exc_val 和exc_tb ,这三个参数是用来接收异常信息的,如果代码在运行时发生异常,异常信息会被保存到这三个参数中.
代码语言:javascript复制class OpenFile(object):
"""自定义上下文管理类"""
def __init__(self, file, mode):
self._file = file
self._mode = mode
def __enter__(self):
self._handle = open(self._file, self._mode)
return self._handle
def __exit__(self, exc_type, exc_val, exc_tb):
print('Type: ', exc_type)
print('Value:', exc_val)
print('TreacBack:', exc_tb)
self._handle.close()
with OpenFile('test01.txt', 'r') as f:
f.write('自定义上下文管理n')
上面的代码中,传入的'w'换成了'r',会出现异常,因为以读的模式打开文件,不能进行写操作.运行结果为:
代码语言:javascript复制Traceback (most recent call last):
Type: <class 'io.UnsupportedOperation'>
File "python_demo/with_demo.py", line 45, in <module>
Value: not writable
f.write('自定义上下文管理n')
TreacBack: <traceback object at 0x0000024EA5E11508>
io.UnsupportedOperation: not writable
可以看到我们打印的三个参数exc_type ,exc_val 和exc_tb的值.分别存储了异常的以下信息:
exc_type : 异常类型
exc_val : 异常值
exc_tb : 异常回溯追踪
当with中执行的语句发生异常时,异常信息会被发送到 __exit__ 方法的参数中,这时可以根据情况选择如何处理异常.
因为在 __exit__函数执行异常处理时,会根据函数的返回值决定是否将系统抛出的异常继续向外抛出.如果返回值为 False 就会向外抛出,用户就会看到.如果返回值为 True 不会向外抛出,而是显示我们自定义的信息.
我们可以根据这个原理自定义如何处理异常,只要将返回值设置为True就行了.
代码语言:javascript复制class OpenFile(object):
"""自定义上下文管理类"""
def __init__(self, file, mode):
self._file = file
self._mode = mode
def __enter__(self):
self._handle = open(self._file, self._mode)
return self._handle
def __exit__(self, exc_type, exc_val, exc_tb):
self._handle.close()
# 通过exc_type参数接收到的值,来判断程序执行是否出现异常
# 如果是None,说明没有异常
if exc_type == "None":
print('正常执行')
else:
# 否则出现异常,可以选择怎么处理异常
print(exc_type, exc_val)
# 返回值决定了捕获的异常是否继续向外抛出
# 如果是False那么就会继续向外抛出,程序会看到系统提示的异常信息
# 如果是True不会向外抛出,程序看不到系统提示信息,只能看到else中的输出
return True
with OpenFile('test01.txt', 'r') as f:
f.write('自定义上下文管理n')
通过设置返回值为True,上下文管理器不会向外抛出异常,此时我们在根据exc_type(异常类型)的值是否为"None"来自定义输出的异常信息,就可以实现上下文管理中的自定义异常了.