python之with语句和上下文管理器

一个类只要实现了__enter__()和__exit__()这个两个方法,通过该类创建的对象我们就称之为上下文管理器。

with语句

常规对文件进行IO操作我们使用的是open方法,后面为了简便和防止文件未关闭出现了更简单的用法,with open。

比如我们在文件读操作打开文件的时候,在里面进行了写操作,导致没执行到后面的文件关闭操作。

1
2
3
4
5
6
7
8
9
10
11
12
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time : 2020/11/25 13:39
# @Author : 托小尼
# @Email : 646547989@qq.com
# @URI : https://www.diandian100.cn
# @File : test.py

file = open('log.log', 'r')
file.write("我要写入了")
print('能执行到这里吗')
file.close()

输出结果:

1
2
3
4
5
6
Traceback (most recent call last):
File "/Users/tony/PycharmProjects/study/test.py", line 10, in <module>
file.write("我要写入了")
io.UnsupportedOperation: not writable

Process finished with exit code 1

可以看到这种情况就导致了文件没有关闭,就需要我们对其进行异常处理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time : 2020/11/25 13:39
# @Author : 托小尼
# @Email : 646547989@qq.com
# @URI : https://www.diandian100.cn
# @File : test.py
try:
file = open('log.log', 'r')
file.write("我要写入了")
except Exception as e:
print(e)
finally:
print('能执行到这里吗')
file.close()

输出结果:

1
2
3
4
not writable
能执行到这里吗

Process finished with exit code 0

改进后的代码即使出现了异常,我们最后也将文件给关闭了。

Python提供了 with 语句的这种写法,既简单又安全,并且 with 语句执行完成以后自动调用关闭文件操作,即使出现异常也会自动调用关闭文件操作

上下文管理器

上下文管理器可以使用 with 语句,with语句之所以这么强大,背后是由上下文管理器做支撑的,也就是说刚才使用 open 函数创建的文件对象就是就是一个上下文管理器对象。

自定义上下文管理器类,模拟文件操作:

定义一个File类,实现 __enter__() 和 __exit__()方法,然后使用 with 语句来完成操作文件, 示例代码:

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
30
31
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time : 2020/11/25 13:39
# @Author : 托小尼
# @Email : 646547989@qq.com
# @URI : https://www.diandian100.cn
# @File : test.py


class File(object):
"""文件操作类"""
def __init__(self, filepath, mode):
self.filepath = filepath
self.mode = mode

def __enter__(self):
"""打开文件"""
self.file = open(self.filepath, self.mode)
print("打开文件")
return self.file

def __exit__(self, exc_type, exc_val, exc_tb):
"""关闭文件"""
print("关闭文件")
self.file.close()


if __name__ == '__main__':
with File('log.log', 'r') as file:
file.write("家啊")

输出结果:

1
2
3
4
5
6
7
8
打开文件
关闭文件
Traceback (most recent call last):
File "/Users/tony/PycharmProjects/study/test.py", line 29, in <module>
file.write("家啊")
io.UnsupportedOperation: not writable

Process finished with exit code 1

可以看到,即使中间我们出现了异常,文件最后依然被关闭了。即:

  • __enter__表示上文方法,需要返回一个操作文件对象
  • __exit__表示下文方法,with语句执行完成会自动执行,即使出现异常也会执行该方法。

装饰器实现上下文

上面的方式为类实现上下文的,假如想要让一个函数成为上下文管理器,Python 还提供了一个 @contextmanager 的装饰器,更进一步简化了上下文管理器的实现方式。通过 yield 将函数分割成两部分,yield 上面的语句在 __enter__ 方法中执行,yield 下面的语句在 __exit__ 方法中执行,紧跟在 yield 后面的参数是函数的返回值。

我们只需要在最开始的代码稍微改装下即可:

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
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time : 2020/11/25 13:39
# @Author : 托小尼
# @Email : 646547989@qq.com
# @URI : https://www.diandian100.cn
# @File : test.py
from contextlib import contextmanager

@contextmanager
def file_handle(filepath, mode):
"""文件操作"""
try:
file = open(filepath, mode)
yield file
except Exception as e:
print(e)
finally:
print('能执行到这里吗')
file.close()


if __name__ == '__main__':
with file_handle('log.log', 'r') as f:
f.write("写入东西")

输出结果:

1
2
3
4
not writable
能执行到这里吗

Process finished with exit code 0

总结

  • Python 提供了 with 语句用于简化资源释放的操作,使用 with 语句操作建立在上下文管理器(实现__enter__和__exit__)的基础上
  • Python 还提供了一个 @contextmanager 装饰器,更进一步简化上下管理器的实现,让一个函数可以成为上下文管理器,结合 with 语句来使用