什么是 Python
的上下文管理器(Context Managers
)呢?
含有 __enter__
和 __exit__
方法的对象就是。上下文管理器存在的目的是管理 with
语句,就像是迭代器的存在是为了管理 for
语句一样。
with 上下文管理器:
语句体
当 with
遇到上下文管理器,就会在执行语句体之前,先执行上下文管理器的 __enter__
方法,然后再执行语句体,执行完语句体后,最后执行 __exit__
方法。举个例子:
class Foo(object):
def __init__(self):
print('把大象放冰箱总共分几步?')
def __enter__(self):
print('把冰箱门打开')
def __exit__(self, exc_type, exc_val, exc_tb):
print('把冰箱门关上')
obj = Foo()
with obj:
print('把大象放进去')
输出:
把大象放冰箱总共分几步?
把冰箱门打开
把大象放进去
把冰箱门关上
with
有一个很重要的作用是简化 try/finally
模式,无论何种异常发生,finally
字句都会执行。
通常 finally
子句中的代码用于释放重要的资源,或者还原临时变更的状态。比如,不管大象能不能装进冰箱,最终 finally
你都得把冰箱门关上,用代码解释就是:
class Foo(object):
def __init__(self):
print('把大象放冰箱总共分几步?')
def __enter__(self):
print('把冰箱门打开')
def __exit__(self, exc_type, exc_val, exc_tb):
print('把冰箱门关上')
obj = Foo()
with obj:
raise ImportError # 这里发生严重的错误,这段程序崩溃了
print('把大象放进去')
输出结果:
把大象放冰箱总共分几步?
把冰箱门打开
把冰箱门关上
Traceback (most recent call last):
File "char_1.py", line 15, in <module>
raise ImportError
ImportError
有了 with
保驾护航,装大象的程序崩溃了,也能把冰箱门关上。
Python
提供了更简便的上下文管理器写法,使用 @contextmanager
。
在使用 @contextmanager
装饰的生成器中,yield
语句的作用是把所在函数分成两部分:
yield
语句前面的代码在 with
块开始时执行;yield
语句后面的代码在 with
块结束时执行;那么上面的例子就可以简写成为:
from contextlib import contextmanager
@contextmanager
def fridge_operation():
print('把大象放冰箱总共分几步?')
print('把冰箱门打开')
yield
print('把冰箱门关上')
def put_elephant_in_fridge():
print('把大象放进去')
with fridge_operation() :
put_elephant_in_fridge()
输出:
把大象放冰箱总共分几步?
把冰箱门打开
把大象放进去
把冰箱门关上
但需要特别注意的是,如果使用 @contextmanager
来实现上下文管理器,要把 yield
语句放在 try/finally
语句中(或者放在 with
语句中)。
个人觉得 Python
在这一点上把 @contextmanager
变的十分具有迷惑性,它可以把生成器函数变成上下文管理器,但是它不能联手与 with
默认完成 finally
功能,用代码说明:
from contextlib import contextmanager
@contextmanager
def fridge_operation():
print('把大象放冰箱总共分几步?')
print('把冰箱门打开')
yield
print('把冰箱门关上')
def put_elephant_in_fridge():
print('把大象放进去')
with fridge_operation() :
raise ImportError # 这里发生严重的错误,这段程序崩溃了
put_elephant_in_fridge()
输出:
把大象放冰箱总共分几步?
把冰箱门打开
Traceback (most recent call last):
File "char_1.py", line 15, in <module>
raise ImportError
ImportError
发现了么?冰箱门没关上。冰箱门没关上,我要你上下文管理器有什么用?所以我们需要把 yield
语句放在 try/finally
语句中,如下:
from contextlib import contextmanager
@contextmanager
def fridge_operation():
print('把大象放冰箱总共分几步?')
print('把冰箱门打开')
try:
yield
finally:
print('把冰箱门关上')
with fridge_operation():
raise ImportError
print('把大象放进去')
输出:
把大象放冰箱总共分几步?
把冰箱门打开
把冰箱门关上
Traceback (most recent call last):
File "char_1.py", line 13, in <module>
raise ImportError
ImportError
至此,@contextmanager
巧妙的将三个不同的 Python
特性结合到了一起:函数装饰器,生成器和 with
语句。
参考:
https://book.pythontips.com/en/latest/context_managers.html