Python之上下文管理器和with语句
# 上下文管理器简介
在任何一门编程语言中,文件的打开和关闭,数据库的连接和释放都是常见的操作。但是资源都是有限的,在写程序时必须保证资源在未使用时会合理的释放,不然很容易造成资源泄露。
在 python 中为了避免程序造成资源浪费而采用的机制是上下文管理其(context manager)。上下文管理其是能够帮助你自动分配并且释放资源,其中最典型的应用便是 with 语句。
比如我们在打开文件时:
with open("test.txt", "w") as f:
f.write("Hello World!")
2
在程序执行完后它内部会自动调用 close()来释放资源,并不需要我们专门去调用一下 close()。
当然还可以写成下面这种:
f = open("test.txt", "w")
try:
f.write("Hello World!")
finally:
f.close()
2
3
4
5
如果采用下面这种写法,finally 代码块是必须的,不然就会造成资源浪费。不过和 with 语句相比较就可以很容易的看到 with 语句代码比较清晰简洁,所以推荐使用 with 语句,也可以避免因为自己疏忽忘记 close()造成不必要的浪费。
还有一个典型的例子是 python 中使用 Threading.lock 类,比如我想获取一个锁,完成后释放,那么可以写成下面这段代码:
my_lock = threading.Lock()
my_lock.acquire()
try:
......
finally:
my_lock.release()
2
3
4
5
6
如果用 with 语句,则可以写成下面这样子:
my_lock = threading.Lock()
my_lock.acquire()
with my_lock:
......
2
3
4
# 上下文管理器应用
# 基于类的上下文管理器
定义一个类 FileManager,模拟文件的打开、关闭操作:
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
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
需要注意的是当我们用类来创建上下文管理器的时候,必须保证这个类包括方法"enter()"和"exit()",其中"enter()"返回一个需要被管理的资源,"exit()"会存在一些释放和清理资源的操作。
我们使用 with 代码块来执行文件的操作它们依次执行下面四个操作:
(1)、调用init(),完成 FileManager 初始化;
(2)、调用enter(),以指定的的模式打开文件,返回打开后的文件对象;
(3)、完成文件操作;
(4)、调用exit(),关闭清理打开的文件流;
另外方法exit()方法中的 exc_type,exc_val,exc_tb 分别表示 exception_type,exception_value,traceback。当执行语句中含有上下文管理器 with 语句时,如果有异常抛出,异常的信息就会包含这三个变量中,传入exit()中。
如果要处理异常,我们可以把代码写成如下:
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
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
这里我们在 with 语句中抛出了异常"exception raised",这时候exit()方法会顺利捕捉到异常并进行处理。不过需要注意的是,如果方法exit中没有返回 True,异常依然会抛出。因此,如果确定异常被处理了,请在exit()的最后加上"return True"。
同样数据库的连接操作,也常常可以使用上下文管理器来表示,简短代码如下:
class DBConnectionManager:
def __init__(self, hostname, port):
self.hostname = hostname
self.port = port
self.connection = None
def __enter__(self):
self.connection = DBClient(self.hostname, self.port)
return self
def __exit__(self, exc_type, exc_val, exc_tb):
self.connection.close()
with DBConnectionManager('localhost', '8080') as db_client:
......
2
3
4
5
6
7
8
9
10
11
12
13
14
15
它的执行过程与 FileManager 类似。
# 基于生成器的上下文管理器
上下文管理器不仅可以通过类来实现,还可以通过生成器的方式。
基于生成器的上下文管理器可以使用 contextlib.contextmanager 来实现,如下:
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')
2
3
4
5
6
7
8
9
10
11
12
13
代码中 file_manager 是一个生成器,当我们使用 with 语句便会打开文件并返回文件对象,当文件执行完后会执行 finally 代码块关闭文件。
基于类的上下文管理器和基于生成器的上下文管理器实现的功能是一样的,不过他们也有一些区别:
(1)、基于类的上下文管理器更加灵活,适用于大型的系统开发;
(2)、基于生成器的上下文管理更加方便简洁,适用于中小型系统开发;
作者:
本文链接:https://jokerbai.com
版权声明:本博客所有文章除特别声明外,均采用 署名-非商业性-相同方式共享 4.0 国际 (CC-BY-NC-SA-4.0) 许可协议。转载请注明出处!
- 02
- 使用Zadig从0到1实现持续交付平台07-19
- 03
- 基于Jira的运维发布平台07-19