0%

Python_上下文管理器

上下文处理器是用来管理with语句.与之对标的概念就是迭代器用来管理for语句.

Q: 上面提到了with语句的作用是什么?

A: with语句的目的是简化try/finally模式,这种模式用于保证一段代码执行完毕后执行某操作,即使这段代码由于异常,return或者sys.exit()调用而终止.

上下文管理器的构成

上下文管理器协议包含有两个方法:__enter__方法,__exit__方法.

  • with语句开始运行时,会在上下文管理器对象上调用__enter__方法.

  • with语句运行结束后,会在上下文管理器对象上调用__exit__方法,扮演finally子句的作用.

示例

1
2
3
4
5
6
7
8
9
10
with open("mirror.py") as fp:
src = fp.read(60)
print(len(src))
# 60
print(fp)
# <_io.TextIOWrapper name='mirror.py' mode='r' encoding='cp936'>
print(fp.closed , fp.encoding)
# True cp936
fp.read(60)
# ValueError: I/O operation on closed file.

**注意:**在第一行语句中分为两部分:

  • 执行with后面的表达式得到的结果时上下文管理器对象.
  • 执行as语句,是此对象执行__enter__方法返回的结果.
    • open()方法本身提供的__enter__方法返回的是本身,所以可以看到fp变量指向的依旧是io.TextIPWrapper

不管控制流程以何种方式退出,都会在上下文管理器对象上调用__exit__方法,而不是__enter__返回的方法.

深入理解上下文管理器的执行过程

像生成器的学习方式一样,我们既可以使用with语句隐式的调用上下文管理器的相关方法,也可以直接调用上下文管理器的相关方法以达到相同的目的.

定义一个上下文管理器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class LookingGlass:
# __enter__的输入参数,只有self本身
def __enter__(self): # <1>
import sys
self.original_write = sys.stdout.write # <2>
sys.stdout.write = self.reverse_write # <3>
return 'JABBERWOCKY' # <4>

def reverse_write(self, text): # <5>
self.original_write(text[::-1])
# __exit__的输入参数,包括其他
# exc_type 异常类 , exc_value异常实例 , traceback traceback对象
def __exit__(self, exc_type, exc_value, traceback): # <6>
import sys # <7>
sys.stdout.write = self.original_write # <8>
if exc_type is ZeroDivisionError: # <9>
print('Please DO NOT divide by zero!')
return True # <10>

通过定义__enter__与__exit__方法定义了一个上下文管理器,并且再次方法中利用monkey patch 对sys中的输出方法进行的反转输出的操作.

with语句使用上述的上下文管理器

1
2
3
4
5
6
7
8
9
10
11
12
from mirror import LookingGlass
# __enter__返回结果绑定到了what变量上面
with LookingGlass() as what:
print("SUN QING ZHI")
print(what)
print(what)
print("Back to Normal")

# IHZ GNIQ NUS
# YKCOWREBBAJ
# JABBERWOCKY
# Back to Normal

可以看到上面的输出,在位于with语句内部时,print函数调用了上下文管理器中给定的逆输出函数,而跳出之后通过__exit__方法还原的原来的输出函数.

直接调用上下文管理器进行相同操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from mirror import LookingGlass
manager = LookingGlass()
what = manager.__enter__()

print("SUN QING ZHI")
print(what)
manager.__exit__(None , None ,None)

print(what)
print("Back to Normal")

# IHZ GNIQ NUS
# YKCOWREBBAJ
# JABBERWOCKY
# Back to Normal

手动调用了__enter__,__exit__ 两个方法,实现了相同的功能,这也就是with语句内部的逻辑.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 其他操作
from mirror import LookingGlass
with LookingGlass():
print("SUN QING ZHI")
# IHZ GNIQ NUS
1/0
# Please DO NOT divide by zero!
print("SUN QING ZHI")
# SUN QING ZHI
# 正常输出

with LookingGlass():
print("SUN QING ZHI")
# IHZ GNIQ NUS
A = NO_SUCH_NAME
# NameError: name 'NO_SUCH_NAME' is not defined
print("SUN QING ZHI")
# 没输出,上面错误上下文管理器并没有正确捕捉

上面两个例子就是为了更好地理解上下文管理器的执行过程.

contextlib 模块中的提供的上下文管理器工具

应用到上下文管理器的场景其实很多,每次都通过手写管理器难免产生遗漏,为此官方提供了一系列的管理器,应用于不同场景.这里不一一的介绍里面的

@contextmanager: 上下文管理器装饰器,可以应用于生成器函数,将其装饰为一个上下文管理器.

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
"""
A "mirroring" ``stdout`` context manager.

While active, the context manager reverses text output to
``stdout``::

# BEGIN MIRROR_GEN_DEMO_1

>>> from mirror_gen import looking_glass
>>> with looking_glass() as what: # <1>
... print('Alice, Kitty and Snowdrop')
... print(what)
...
pordwonS dna yttiK ,ecilA
YKCOWREBBAJ
>>> what
'JABBERWOCKY'

# END MIRROR_GEN_DEMO_1


This exposes the context manager operation::

# BEGIN MIRROR_GEN_DEMO_2

>>> from mirror_gen import looking_glass
>>> manager = looking_glass() # <1>
>>> manager # doctest: +ELLIPSIS
<contextlib._GeneratorContextManager object at 0x...>
>>> monster = manager.__enter__() # <2>
>>> monster == 'JABBERWOCKY' # <3>
eurT
>>> monster
'YKCOWREBBAJ'
>>> manager # doctest: +ELLIPSIS
>...x0 ta tcejbo reganaMtxetnoCrotareneG_.biltxetnoc<
>>> manager.__exit__(None, None, None) # <4>
>>> monster
'JABBERWOCKY'

# END MIRROR_GEN_DEMO_2

"""


# BEGIN MIRROR_GEN_EX

import contextlib


@contextlib.contextmanager # <1>
def looking_glass():
import sys
original_write = sys.stdout.write # <2>

def reverse_write(text): # <3>
original_write(text[::-1])

sys.stdout.write = reverse_write # <4>
yield 'JABBERWOCKY' # <5>
sys.stdout.write = original_write # <6>


# END MIRROR_GEN_EX

我这里就不一一总结了,了解就行.