01 python上下文管理协议

上下文管理器(context manager) 是 Python2.5 开始支持的一种语法,用于规定 某个对象的使用范围。一旦进入或者离开该使用范围,会有特殊操作被调用 (比如为对象分配或者释放内存)。它的语法形式是 with...as...

所谓上下文管理协议,就是咱们打开文件时常用的一种方法: with

__enter__(self):当with开始运行的时候触发此方法的运行
__exit__(self, exc_type, exc_val, exc_tb):当with运行结束之后触发此方法的运行

exc_type如果抛出异常,这里获取异常的类型
exc_val如果抛出异常,这里显示异常内容
exc_tb如果抛出异常,这里显示所在位置

02 with语句

2.1 示例

相信大家都在文档处理的过程中使用过 with 语句。

1
2
with open("example.txt") as file:
data = file.read()

如果不用 with 语句,等价于

1
2
3
4
5
file = open("example.txt")
try:
data = file.read()
finally:
file.close()

从本质上看, with语句完成了偷偷地完成了两件事:

  1. 事先需要设置,
  2. 2.事后做清理工作。

2.2 with 如何工作

“事前设置,事后清理”,这看起来充满魔法,但不仅仅是魔法。Python对with的处理是基于是所求值的对象必须有一个__enter__()方法,一个__exit__()方法。

下面例子可以具体说明with如何工作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Sample:
def __enter__(self):
print("In __enter__()")
return "Foo"

def __exit__(self, type, value, trace):
print("In __exit__()")

def get_sample():
return Sample()

with get_sample() as s:
print("sample:", s)

## 输出
# In __enter__()
# sample: Foo
# In __exit__()

2.3 with处理异常

with真正强大之处是它可以处理异常。可能你已经注意到Sample类的__exit__方法有三个参数value, typetrace。 这些参数在异常处理中相当有用。我们来改一下代码,看看具体如何工作的。

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
class Sample:
def __enter__(self):
return self

def __exit__(self, type, value, trace):
print("type:", type)
print("value:", value)
print("trace:", trace)

def do_something(self):
bar = 1/0
return bar + 10

with Sample() as sample:
sample.do_something()

## 输出
# type: <class 'ZeroDivisionError'>
# value: division by zero
# trace: <traceback object at 0x7429b3ca7d00>
# Traceback (most recent call last):
# File "/home/xxy/Desktop/baidu/test.py", line 15, in <module>
# sample.do_something()
# File "/home/xxy/Desktop/baidu/test.py", line 11, in do_something
# bar = 1/0
# ZeroDivisionError: division by zero

实际上,在with后面的代码块抛出任何异常时,__exit__()方法被执行。正如例子所示,异常抛出时,与之关联的type,value和stack trace传给__exit__()方法,因此抛出的ZeroDivisionError异常被打印出来了。开发库时,清理资源,关闭文件等等操作,都可以放在__exit__方法当中。