Python 中的上下文管理器
with
expression
[
as
target
]
:
with
-
body
上下文管理器是为with 语句而生。只要实现了上下文管理器协议
__enter__
与
__exit__
,就可以使用with语句。
__enter__
通常执行一些初始化操作,并且该函数的返回值会赋值给可选的
as target
中的
target
变量。
__exit__
执行资源清理工作。它接收三个参数,异常类型,异常实例,和异常栈,根据这些异常信息,
__exit__
可以选择进行相应的异常处理,并默认抛出异常。如果我们在让
__exit__
返回True,相当于告诉python:这些异常我都已经处理了,都在掌控之中,您老不必操心。
除了自定义类手动实现两个特殊方法外,还有另一种途径实现一个上下文管理器。
标准库
contextlib
中提供了一个
@contextmanager
可以方便的把一个协程函数包装成一个上下文管理器。
《Fluent Python》 书中一个好玩的例子:
@contextmanager
def
f
(
)
:
import
sys
print
(
'欢迎来到镜像的世界'
)
origin_print
=
sys
.
stdout
.
write
sys
.
stdout
.
write
=
lambda
x
:
origin_print
(
x
[
:
:
-
1
]
)
# 初始化:替换系统输入。运行中动态修改、添加类的方法————猴子补丁。
yield
'这里的打印都是反向输出'
sys
.
stdout
.
write
=
origin_print
# 退出时:恢复系统输入
print
(
'Finally I come back'
)
mirror_world
=
f
(
)
with
mirror_world
as
target
:
print
(
target
)
输出结果:
欢迎来到镜像的世界
出输向反是都印打的里这
Finally I come back
协程函数中yield之前的所有代码相当于
__enter__
部分的工作,执行初始化,执行中动态替换了系统的输出功能(猴子补丁特性)。
并且把一个结果绑定到
with...as target
的
target
。至此协程函数交出代码执行权,python转而去执行with-block里面的代码。执行完with-block 开始执行yield之后的代码——相当于
__exit__
的工作,执行资源清理。
至此我们好像实现了一个功能正常的上下文管理器。但别忘了还有异常捕获的机制。。。
在终端中执行
mirror_world
时,如果with-block中抛出了一个异常,会导致资源清理工作没有进行,之后所有的print仍是反向输出。我们还应做的是把yield行的代码包裹在一个
try...except...finally
中,在finally-bolck中执行资源清理工作,以保证正常退出(鬼知道用户会在with-block搞什么蛇皮…)。