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抛出错误时可以做一些特殊的逻辑处理。