上下文管理器
上下文管理器
编程中的上下文也与此类似,比如进程上下文,指的是一个进程在执行的时候,
- 上下文管理器协议,是指要实现对象的
__enter__()
和__exit__()
方法。 - 上下文管理器也就是支持上下文管理器协议的对象,也就是实现了
__enter__()
和__exit__()
方法。
这里先构造一个简单的上下文管理器的例子,以理解 __enter__()
和 __exit__()
方法。
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
语句调用上下文管理器:
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 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__
方法返回的是__exit__
方法做一些改动,让它返回
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__
方法返回了
- 上下文管理器是支持上下文管理协议的对象,也就是实现了
__enter__
和__exit__
方法。 - 通常,我们使用
with
语句调用上下文管理器。with 语句尤其适用于对资源进行访问的场景,确保执行过程中出现异常情况时也可以对资源进行回收,比如自动关闭文件等。 __enter__
方法在with 语句体执行前调用,with 语句将该方法的返回值赋给as 字句中的变量,如果有as 字句的话。__exit__
方法在退出运行时上下文
时被调用,它负责执行『清理』工作,比如关闭文件,释放资源等。如果退出时没有发生异常,则__exit__
的三个参数,即type, value 和traceback 都为None 。如果发生异常,返回True 表示不处理异常,否则会在退出该方法后重新抛出异常以由with 语句之外的代码逻辑进行处理。
内建对象使用with 语句
除了自定义上下文管理器,
传统的文件操作经常使用 try/finally
的方式,比如:
file = open('somefile', 'r')
try:
for line in file:
print line
finally:
file.close() # 确保关闭文件
将上面的代码改用
with open('somefile', 'r') as file:
for line in file:
print line
可以看到,通过使用
contextlib 模块
除了在类中定义 __enter__
和 __exit__
方法来实现上下文管理器,我们还可以通过生成器函数(也就是带有
下面我们看一个简单的例子:
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
可以看到,
另外,需要强调的是,虽然通过使用__enter__
和 __exit__
方法,但是『获取』和『清理』资源的操作仍需要我们自己编写