python中进行文件操作,经常被建议使用with open
的方式,形如:
with open('./dec.py', 'r') as f:
for line in f.readlines():
print(line)
why? 这背后的原因是什么?
直观的解释是:这样的好处文件操作是io操作,操作完成之后,需要关闭close,避免文件一直被占用打开。
与之对比另一种写法是:
f = open('./dec.py', 'r')
for line in f.readlines():
print(line)
f.close()
两种写法对比可以知道:with 方式隐式的帮我们做了资源的释放操作,而这个可能是我们可能遗忘的。
那with是什么?有没有其他的作用?就是今天的主题。
1.with 上下文管理
with是一种python上下文管理方式;什么是上下文(context)
- context是指程序运行所处于的环境和自身的状态(如数据)
- 比如上面提到的文件操作,包含了文件打开获取文件操作的句柄权限(获得资源)、处理文件信息、关闭文件(释放资源);这些都是可以认为是文件的操作的环境和状态,也就是一个上下文(context上下文这个译文有点怪怪,可以理解成一个容器内操作一个过程)
所以with上下文管理就是用在某种资源的创建,使用和回收以及异常处理过程中。语法结构如下:
with context_expression [as target(s)]:
with-body
结合文件操作来说:
- context_expression:获取资源,获取文件流
- as target(s): 是可选的,可以认为资源变量
- with-body: 具体的文件操作
2.如何构建自己的上下文管理
with内部是如何实现的? 从上下文管理的概念可以知道:一个是获取资源,一个是释放资源。
与之对应的,只要实现了下面两个协议的都可以用with来进行上下文管理
- __enter__: 实现获取资源
- __exit__: 来实现释放资源的操作
我们来看下具体的例子:
import traceback
class MyWith():
def __enter__(self):
print('enter the resource')
return self
def __exit__(self, exc_type, exc_val, exc_tb):
print(exc_type, exc_val, exc_tb)
print('release the resource')
print(str(traceback.print_exc()))
# 返回TRUE表示程序已经捕获到异常并处理
return True
def with_body(self):
print('this is with body')
with MyWith() as res:
res.with_body()
# enter the resource
# this is with body
# None None None
# release the resource
# None
可以看出 __exit__
包含了with-body
的异常控制的处理和捕捉,返回TRUE表示程序已经捕获到异常并处理。所以即使你在函数with_body
中出现异常情况,程序也会正常执行,除非你在__exit__
直接抛出异常。
可以还原下open的写法:
import traceback
class MyOpen():
def __init__(self, file_name, mode):
self.file_name = file_name
self.mode = mode
self.file = None
def __enter__(self):
print('enter the resource')
self.file = open(self.file_name, self.mode)
return self.file
def __exit__(self, exc_type, exc_val, exc_tb):
print(exc_type, exc_val, exc_tb)
print('release the resource')
print(str(traceback.print_exc()))
if self.file:
self.file.close()
with MyOpen('./dec.py', 'r' ) as f:
for line in f.readlines():
print(line)
3.用contextlib简化
对于需要上下文管理的场景,都要建立一个类,并实现 enter 和 __exit__接口来实现上下文管理;有没有更好的方式。
python提供contextlib装饰器来简化上下文管理,具体如下:
import contextlib
@contextlib.contextmanager
def simple_open(file_name, mode):
# __enter__方法
print('open file:', file_name, 'in __enter__')
file_handler = open(file_name, mode)
try:
yield file_handler
except Exception as exc:
# deal with exception
print('the exception was thrown')
finally:
print('close file:', file_name, 'in __exit__')
file_handler.close()
return
with simple_open('./dec.py', 'r') as f:
for line in f.readlines():
print(line)
可以看出,
contextlib.contextmanager
通过yield来代表资源,- yield前面的获取资源
- 通过try...except来进行异常处理,通过finally来做资源释放