上下文管理器
在使用Python编程中,可以会经常碰到这种情况:
有一个特殊的语句块,在执行这个语句块之前需要先执行一些准备动作;当语句块执行完成后,需要继续执行一些收尾动作。
例如:当需要操作文件或数据库的时候,首先需要获取文件句柄或者数据库连接对象,当执行完相应的操作后,需要执行释放文件句柄或者关闭数据库连接的动作。
又如,当多线程程序需要访问临界资源的时候,线程首先需要获取互斥锁,当执行完成并准备退出临界区的时候,需要释放互斥锁。
对于这些情况,Python中提供了上下文管理器(Context Manager)的概念,可以通过上下文管理器来定义/控制代码块执行前的准备动作,以及执行后的收尾动作。
with语法:
with context_expr [as var]:
with_suite
- context_expr是支持上下文管理协议的对象,也就是上下文管理器对象,负责维护上下文环境
- as var是一个可选部分,通过变量方式保存上下文管理器对象
- with_suite就是需要放在上下文环境中执行的语句块
sample:
# with语句会在执行完代码块后自动关闭文件
with open('test.txt','w') as f:
f.write('hello!')
自定义上下文管理器对象
对于自定义的类型,可以通过实现__enter__
和__exit__
方法来实现上下文管理器:
__enter__(self)
:
- 在语句体执行之前进入运行时上下文
- 如果有as子句,with语句将该方法的返回值赋值给 as 子句中的 target
- 一般用于资源分配,如打开文件、连接数据库、获取线程锁
__exit__(self, exc_type, exc_val, exc_tb)
:
- 在语句体执行完后从运行时上下文退出
- exc_type判断是否有异常
- 一般用于资源释放,如关闭文件、关闭数据库连接、释放线程锁
class MyContextManager():
def __init__(self, a_list):
print('初始化数据')
self.a_list = a_list
# 进入上下环境自动执行,并赋值as语句
def __enter__(self):
print('开始操作数据')
self.a_list.append('python')
return self
# 退出上下文环境自动执行, 回收资源
def __exit__(self, exc_type, exc_val, exc_tb):
self.a_list = None
# 异常触发了
if exc_type:
print('发生异常')
else:
print('正常退出')
def work(self):
for item in self.a_list:
print(item)
with MyContextManager(['a', 'b', 'c', 'd']) as f:
f.work()
输出信息:
初始化数据
开始操作数据
a
b
c
d
python
正常退出
发生异常:
class MyContextManager():
def __init__(self, a_list):
print('初始化数据')
# 进入上下环境自动执行
def __enter__(self):
print('开始操作数据')
# 退出上下文环境自动执行, 回收资源
def __exit__(self, exc_type, exc_val, exc_tb):
self.a_list = None
# 异常触发了
if exc_type:
print(exc_type) # <class 'ZeroDivisionError'>
print(exc_val) # division by zero
print(exc_tb) # <traceback object at 0x000000000C1D0508>
with MyContextManager(['a', 'b', 'c', 'd']):
1 / 0
contextlib
contextlib模块的contextmanager装饰器可以更方便的实现上下文管理器。
任何能够被yield关键词分割成两部分的函数,都能够通过装饰器装饰的上下文管理器来实现。
- 任何在yield之前的内容都可以看做在代码块执行前的操作,
- 而任何yield之后的操作都可以放在exit函数中
yield返回的值相当于
__enter__
的返回值。当出现异常时,yield后的语句是不会执行的,想要异常安全,可用try捕捉异常
from contextlib import contextmanager
class MyContextManger():
def __init__(self, a_list):
print('初始化数据')
self.a_list = a_list
def work(self):
for item in self.a_list:
print(item)
@contextmanager # 管理器对象
def mk_context(a_list):
tls = MyContextManger(a_list)
print('开始处理数据')
try:
yield tls # 抛出来的实例对象
except RuntimeError:
pass
print('处理完毕')
with mk_context(['a', 'b', 'c']) as mk:
mk.work()
输出信息:
初始化数据
开始处理数据
a
b
c
处理完毕
closing
动触发执行内部close(自定义类,写一个close方法)
from contextlib import closing
class MyClass():
def __init__(self):
print('初始化数据')
def work(self):
print('执行数据')
def close(self):
print('关闭数据资源')
with closing(MyClass()) as f:
f.work()
输出信息:
初始化数据
执行数据
关闭数据资源