什么是上下文管理器
上下文管理器是一个对象,它定义了在执行 with 语句时要建立的运行时上下文。 上下文管理器处理进入和退出所需运行时上下文以执行代码块。 通常使用 with 语句(在 with 语句中描述),但是也可以通过直接调用它们的方法来使用。
首先我们看下面操作文件的代码,理清几个概念,不要弄混了
with open("test.txt") as f:
print(f.readlines())
-
with open("test.txt") as f
:上下文表达式 -
open("test.txt")
:上下文管理器 -
f
:至于f,f不是上下文管理器,f应该是资源对象
上下文管理协议
- 在一个类中,如果实现了
__enter__
和__exit__
这两个魔法方法,这个类的实例就是一个上下文管理器。 - 如果使用了上下文管理器,尽管with没有调用魔法方法,但是with在代码块执行前还是会先执行
__enter__
,在代码执行结束或出错的时候执行__enter__
-
__enter__
: with语句中的代码块执行前执行__enter__
, 返回的值将赋值给with句中as后的变量. -
__exit__
: with语句中的代码块执行结束或出错, 会执行__exit__
class Resource():
def __enter__(self):
print('===connect to resource===')
return self
def __exit__(self, exc_type, exc_val, exc_tb):
print('===close resource connection===')
def operate(self):
print('===in operation===')
with Resource() as res:
res.operate()
"""
输出
===connect to resource===
===in operation===
===close resource connection===
"""
- 在编写代码时,我们一般将资源的连接或者获取放在
__enter__
中,而将资源的关闭写在__exit__
中。
为什么要使用上下文管理器
- 使用上下文管理器会让代码看起来更简洁优雅,这也是Python一直追求的。我们可以用上下文管理器操作(创建/获取/释放)资源,如文件操作、数据库连接;
- 也可以用上下文管理器处理异常。我们一般用try...except...来处理异常但是这样做一个不好的地方是,在代码的主逻辑里,会有大量的异常处理代理,这会很大的影响我们的可读性。如果用上下文管理器,就可以使用with将异常的处理隐藏起来。(也就是说,with大大简化了try...except..语句的异常处理)
举个栗子,下面的代码我们将操作1/0
这个错误。看看是否不报错。
class Resource():
def __enter__(self):
print('===connect to resource===')
return self
def __exit__(self, exc_type, exc_val, exc_tb):
print('===close resource connection===')
print(exc_type, exc_val, exc_tb)
return True
def operate(self):
1/0 # 分母不能为0,这里应该报错
with Resource() as res:
res.operate()
"""
输出
===connect to resource===
===close resource connection===
division by zero
"""
这就是上下文管理协议的一个强大之处,异常可以在__exit__
进行捕获并由你自己决定如何处理,是抛出呢还是在这里就解决了。在__exit__
里返回 True
(没有return 就默认为 return False),就相当于告诉 Python解释器,这个异常我们已经捕获了,不需要再往外抛了。
在 写__exit__
函数时,需要注意的事,它必须要有这三个参数:
- exc_type:异常类型
- exc_val:异常值
- exc_tb:异常的错误栈信息
当主逻辑代码没有报异常时,这三个参数将都为None。
理解并使用装饰器 contextlib
上面说了,如果要定义上下文管理器,就需要在类中定义
__enter__
和__exit__
。在Python中也提供了一个@contextlib
装饰器,可以省略两个魔法方法。该装饰器位于contextlib模块下
from contextlib import contextmanager
- 我们借助contextmanager装饰器,可以不使用两个魔法方法。但是这里注意我们只是不需要定义
__enter__
和__exit__
这两个方法,但是他们里面所执行的语句我们还是需要实现的。在进入上下文管理器的时候打印__enter__
里面的方法,在退出的时候打印__exit__
里面的方法。
from contextlib import contextmanager
@contextmanager
def open_func(file_name):
# __enter__ 方法
print('open file:', file_name, 'in __enter__')
file_handler = open(file_name, 'r')
# 【重点】:yield 返回的内容复制给as之后的变量
yield file_handler
# __exit__方法
print('close file:', file_name, 'in __exit__')
file_handler.close()
return
with open_func('E:/hello.txt') as f:
for line in f:
print(line)
"""
输出
open file: E:/hello.txt in __enter__
1
2
3
close file: E:/hello.txt in __exit__
"""
上面代码的执行过程:
- with语句中的代码块执行函数中yield语句之前的代码,相当于执行
__enter__
方法。 - yield返回的内容复制给as之后的变量,也就是f。 在被装饰函数里,必须是一个生成器(带有yield)
- with语句中的代码块执行函数中yield语句之后的代码,相当于执行
__exit__
方法。
上下文管理器的三个好处
- 提高代码的复用率
- 提高代码的优雅度
- 提高代码的可读性