前言
上下文管理器(context manager),其作用能够帮助自动分配并且释放资源,其中最典型的便是 with
语句:
1
2
3
for x in range(10000000):
with open('test.txt', 'w') as f:
f.write('hello')
其等效于:
1
2
3
4
5
f = open('test.txt', 'w')
try:
f.write('hello')
finally:
f.close()
可以看到,采用 with
的结构会更加简洁。上下文管理器,通常应用在文件的开关,数据库的开关,线程的lock/release等操作,可以确保用过的资源得到迅速释放,能够有效提高程序的安全性。 从原理上讲,一般分为两种上下文管理器,其功能上是一致的:
- 基于生成器的上下文管理器:方便简洁,适用于中小型程序
- 基于类的上下文管理器:更加灵活,适用于大型系统开发
基于类的上下文管理器
类的上下文管理器,关键在于一些关键方法的定义:
__init__
:创建类的初始化方法__enter__
:返回需要被管理的资源__exit__
:通常会存在一些释放、清理资源的操作,比如关闭文件等
以创建文件的开关创建的上下文管理器 FileManager
为例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class FileManager:
def __init__(self, name, mode):
print('calling __init__ method')
self.name = name
self.mode = mode
self.file = None
def __enter__(self):
print('calling __enter__ method')
self.file = open(self.name, self.mode)
return self.file
def __exit__(self, exc_type, exc_val, exc_tb):
print('calling __exit__ method')
if self.file:
self.file.close()
with FileManager('test.txt', 'w') as f:
print('ready to write to file')
f.write('hello world')
## 输出
calling __init__ method
calling __enter__ method
ready to write to file
calling __exit__ method
另外对于 __exit__
方法来说,可以用于管理异常(exception),可以通过以下参数: exec_type/exc_val/exc_tb
,分别表示: exception_type/exeception_value/traceback
。当执行含有上下文管理器的 with
语句时,如果有异常抛出,异常的信息则会包含在这三个变量,传入 __exit__()
方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class Foo:
def __init__(self):
print('__init__ called')
def __enter__(self):
print('__enter__ called')
return self
def __exit__(self, exc_type, exc_value, exc_tb):
print('__exit__ called')
if exc_type:
print(f'exc_type: {exc_type}')
print(f'exc_value: {exc_value}')
print(f'exc_traceback: {exc_tb}')
print('exception handled')
return True
with Foo() as obj:
raise Exception('exception raised').with_traceback(None)
# 输出
__init__ called
__enter__ called
__exit__ called
exc_type: <class 'Exception'>
exc_value: exception raised
exc_traceback: <traceback object at 0x1046036c8>
exception handled
但是,值得注意的是:需要在 __exit__
方法最后需要返回 True
,否则依然会抛出异常。
基于生成器的上下文管理器
可以采用装饰器 contextlib.contextmanager
来定义上下文管理器,用以支持 with
语句:
上述代码中, file_manager()
为生成器,返回的是文件对象f,当 with
语句执行完后, finally block
会执行关闭文件操作。 这里不需要定义 __enter__()
和 __exit__()
方法,但是需要依赖 @contextmanager
1
2
3
4
5
6
7
8
9
10
11
12
13
from contextlib import contextmanager
@contextmanager
def file_manager(name, mode):
try:
f = open(name, mode)
yield f
finally:
f.close()
with file_manager('test.txt', 'w') as f:
f.write('hello world')