forked from ethan-funny/explore-python
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcontext.md
More file actions
177 lines (126 loc) · 7.79 KB
/
Copy pathcontext.md
File metadata and controls
177 lines (126 loc) · 7.79 KB
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
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
# 上下文管理器
什么是上下文?其实我们可以简单地把它理解成环境。从一篇文章中抽出一句话,让你来理解,我们会说这是断章取义。为什么?因为我们压根就没考虑到这句话的上下文是什么。编程中的上下文也与此类似,比如『进程上下文』,指的是一个进程在执行的时候,CPU 的所有寄存器中的值、进程的状态以及堆栈上的内容等,当系统需要切换到其他进程时,系统会保留当前进程的上下文,也就是运行时的环境,以便再次执行该进程。
迭代器有[迭代器协议(Iterator Protocol)](./iterator.md),上下文管理器(Context manager)也有上下文管理协议(Context Management Protocol)。
- **上下文管理器协议**,是指要实现对象的 `__enter__()` 和 `__exit__()` 方法。
- **上下文管理器**也就是支持上下文管理器协议的对象,也就是实现了 `__enter__()` 和 `__exit__()` 方法。
这里先构造一个简单的上下文管理器的例子,以理解 `__enter__()` 和 `__exit__()` 方法。
```python
from math import sqrt, pow
class Point(object):
def __init__(self, x, y):
print 'initialize x and y'
self.x, self.y = x, y
def __enter__(self):
print "Entering context"
return self
def __exit__(self, type, value, traceback):
print "Exiting context"
def get_distance(self):
distance = sqrt(pow(self.x, 2) + pow(self.y, 2))
return distance
```
上面的代码定义了一个 `Point` 类,并实现了 `__enter__()` 和 `__exit__()` 方法,我们还定义了 `get_distance` 方法,用于返回点到原点的距离。
通常,我们使用 `with` 语句调用上下文管理器:
```python
with Point(3, 4) as pt:
print 'distance: ', pt.get_distance()
# output
initialize x and y # 调用了 __init__ 方法
Entering context # 调用了 __enter__ 方法
distance: 5.0 # 调用了 get_distance 方法
Exiting context # 调用了 __exit__ 方法
```
上面的 `with` 语句执行过程如下:
- Point(3, 4) 生成了一个上下文管理器;
- 调用上下文管理器的 `__enter__()` 方法,并将 `__enter__()` 方法的返回值赋给 as 字句中的变量 pt;
- 执行**语句体**(指 with 语句包裹起来的代码块)内容,输出 distance;
- 不管执行过程中是否发生异常,都执行上下文管理器的 `__exit__()` 方法。`__exit__()` 方法负责执行『清理』工作,如释放资源,关闭文件等。如果执行过程没有出现异常,或者语句体中执行了语句 break/continue/return,则以 None 作为参数调用 `__exit__(None, None, None) `;如果执行过程中出现异常,则使用 `sys.exc_info` 得到的异常信息为参数调用 `__exit__(exc_type, exc_value, exc_traceback)`;
- 出现异常时,如果 `__exit__(type, value, traceback)` 返回 False 或 None,则会重新抛出异常,让 with 之外的语句逻辑来处理异常;如果返回 True,则忽略异常,不再对异常进行处理;
上面的 with 语句执行过程没有出现异常,我们再来看出现异常的情形:
```python
with Point(3, 4) as pt:
pt.get_length() # 访问了对象不存在的方法
# output
initialize x and y
Entering context
Exiting context
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-216-ab4a0e6b6b4a> in <module>()
1 with Point(3, 4) as pt:
----> 2 pt.get_length()
AttributeError: 'Point' object has no attribute 'get_length'
```
在我们的例子中,`__exit__` 方法返回的是 None(如果没有 return 语句那么方法会返回 None)。因此,with 语句抛出了那个异常。我们对 `__exit__` 方法做一些改动,让它返回 True。
```python
from math import sqrt, pow
class Point(object):
def __init__(self, x, y):
print 'initialize x and y'
self.x, self.y = x, y
def __enter__(self):
print "Entering context"
return self
def __exit__(self, type, value, traceback):
print "Exception has been handled"
print "Exiting context"
return True
def get_distance(self):
distance = sqrt(pow(self.x, 2) + pow(self.y,2 ))
return distance
with Point(3, 4) as pt:
pt.get_length() # 访问了对象不存在的方法
# output
initialize x and y
Entering context
Exception has been handled
Exiting context
```
可以看到,由于 `__exit__` 方法返回了 True,因此没有异常会被 with 语句抛出。
# 内建对象使用 with 语句
除了自定义上下文管理器,Python 中也提供了一些内置对象,可直接用于 with 语句中,比如最常见的文件操作。
传统的文件操作经常使用 `try/finally` 的方式,比如:
```python
file = open('somefile', 'r')
try:
for line in file:
print line
finally:
file.close() # 确保关闭文件
```
将上面的代码改用 with 语句:
```python
with open('somefile', 'r') as file:
for line in file:
print line
```
可以看到,通过使用 with,代码变得很简洁,而且即使处理过程发生异常,with 语句也会确保我们的文件被关闭。
# contextlib 模块
除了在类中定义 `__enter__` 和 `__exit__` 方法来实现上下文管理器,我们还可以通过生成器函数(也就是带有 yield 的函数)结合装饰器来实现上下文管理器,Python 中自带的 contextlib 模块就是做这个的。
contextlib 模块提供了三个对象:装饰器 contextmanager、函数 nested 和上下文管理器 closing。其中,contextmanager 是一个装饰器,用于装饰生成器函数,并返回一个上下文管理器。需要注意的是,被装饰的生成器函数只能产生一个值,否则会产生 RuntimeError 异常。
下面我们看一个简单的例子:
```python
from contextlib import contextmanager
@contextmanager
def point(x, y):
print 'before yield'
yield x * x + y * y
print 'after yield'
with point(3, 4) as value:
print 'value is: %s' % value
# output
before yield
value is: 25
after yield
```
可以看到,yield 产生的值赋给了 as 子句中的 value 变量。
另外,需要强调的是,虽然通过使用 contextmanager 装饰器,我们可以不必再编写 `__enter__` 和 `__exit__` 方法,但是『获取』和『清理』资源的操作仍需要我们自己编写:『获取』资源的操作定义在 yield 语句之前,『释放』资源的操作定义在 yield 语句之后。
# 小结
- 上下文管理器是支持上下文管理协议的对象,也就是实现了 `__enter__` 和 `__exit__` 方法。
- 通常,我们使用 `with` 语句调用上下文管理器。with 语句尤其适用于对资源进行访问的场景,确保执行过程中出现异常情况时也可以对资源进行回收,比如自动关闭文件等。
- `__enter__` 方法在 with 语句体执行前调用,with 语句将该方法的返回值赋给 as 字句中的变量,如果有 as 字句的话。
- `__exit__` 方法在退出`运行时上下文`时被调用,它负责执行『清理』工作,比如关闭文件,释放资源等。如果退出时没有发生异常,则 `__exit__` 的三个参数,即 type, value 和 traceback 都为 None。如果发生异常,返回 True 表示不处理异常,否则会在退出该方法后重新抛出异常以由 with 语句之外的代码逻辑进行处理。
# 参考资料
- [编程中什么是「Context(上下文)」? - 知乎](https://www.zhihu.com/question/26387327)
- [浅谈 Python 的 with 语句](https://www.ibm.com/developerworks/cn/opensource/os-cn-pythonwith/)
- [上下文管理器 · Python进阶](https://eastlakeside.gitbooks.io/interpy-zh/content/context_managers/)