5.6 定义enter()和exit()方法
上下文管理器的定义包含两个特殊方法:enter()和exit()。with语句使用它们进行上下文的进入和退出。接下来会通过一个示例来进行说明。
我们经常使用上下文管理器来执行短暂的全局修改。可能是数据库事务状态的改变或者是锁状态的改变,亦或一些事情,只希望在事务结束前执行的逻辑,而事务结束后可以被移除。
接下来的例子中,在全局上改变了随机数生成器。我们会创建一个上下文,在这个上下文内随机数生成器使用一个固定的、已知的随机种子来生成固定的值。
以下是这个上下文管理器类的定义。
import random
class KnownSequence:
def init(self, seed=0):
self.seed= 0
def enter(self):
self.was= random.getstate()
random.seed(self.seed, version=1)
return self
def exit(self, exc_type, exc_value, traceback):
random.setstate(self.was)
我们定义了所需的enter()和exit()方法。enter()方法会保存随机模块上次的状态并将种子重置为设定的值。exit()方法用于恢复随机数生成器之前的状态。
注意,enter()方法返回了self。这点对于被添加到了其他类定义中的mixin上下文管理器来说是常见的。我们会在第8章“装饰器和mixin——横切方面”中进行介绍。
exit()方法的参数值在正常情况下会被赋值为None。除非我们有特殊的异常处理需要,我们通常会忽略参数值。在以下代码中介绍异常处理的过程。
这里是一个使用上下文的例子。
print( tuple(random.randint(-1,36) for i in range(5)) )
with KnownSequence():
print( tuple(random.randint(-1,36) for i in range(5)) )
print( tuple(random.randint(-1,36) for i in range(5)) )
with KnownSequence():
print( tuple(random.randint(-1,36) for i in range(5)) )
print( tuple(random.randint(-1,36) for i in range(5)) )
每次创建一个KnownSequence的实例,修改了random模块的实现。在with语句的上下文中,会得到一串固定的随机数。而在上下文之外,由于随机种子被复原了,因此会得到随机的数值。
输出可能会是如下这样(大多数情况下)。
(12, 0, 8, 21, 6)
(23, 25, 1, 15, 31)
(6, 36, 1, 34, 8)
(23, 25, 1, 15, 31)
(9, 7, 13, 22, 29)
以上结果可能会因机器不同而改变。然而其他行可能会不一样,可第2行与第4行一定是一致的,因为种子已经在上下文中被固定了。其他行没必要相同,因为它们取决于随机模块自身的随机功能。
异常处理
抛出的异常会传给上下文管理器中的exit()函数。异常的标准信息——类,参数和追踪栈——都会作为参数值传入。
exit()方法可以使用异常信息做如下两件事情。
- 通过返回一些True的值把异常吞掉。
- 通过返回其他一些False的值允许异常正常抛出。什么都不返回和返回None是一样的,都是一个False值,这将允许异常向上冒泡。
异常也可用于改变上下文管理器在退出时的行为。比如,可能希望当OS抛出错误时可以做一些特殊的逻辑处理。
