5.7 上下文管理器工厂

    可以创建一个上下文管理器类来作为应用程序对象的工厂。这样的设计使得耦合降低,而且无需在应用程序类编写过多有关上下文管理器功能的逻辑。

    假如需要一个Deck类来完成21点中的发牌。可它并非像听起来那样有用。对于单元测试,将需要一个完整的、模拟的deck对象和特殊序列的牌。它有一个优势,正如之前看到的,可以和上下文管理器类一起工作。

    我们将扩展以上演示的简单上下文管理器:创建Deck类,它可以在with语句上下文中使用。

    以下是一个类,它是Deck的工厂,改变了random模块的实现。

    class DeterministicDeck:
      def init( self, args, **kw ):
        self.args= args
        self.kw= kw
      def enter( self ):
        self.was= random.getstate()
        random.seed( 0, version=1 )
        return Deck(
    self.args, **self.kw )
      def _exit
    ( self, exc_type, exc_value, traceback ):
        random.setstate( self.was )

    以上的上下文管理器类存了参数值,为了用于创建Deck

    enter()方法存了旧的随机数状态,然后将随机数模块的逻辑改为:产生固定的随机值,用来创建和洗牌。

    注意enter()方法返回了一个新的Deck对象,用于with语句的上下文中,并使用withas语句为其赋值。

    也可以考虑另一种方式达到同样的目的。可以在Deck类中创建一个random. Random(x=seed)实例。它也会工作,由于代码仅用于展示,使得Deck类显得更混乱。

    以下是使用这个上下文管理器工厂的方式。

    with Deterministic_Deck( size=6 ) as deck:
      h = Hand( deck.pop(), deck.pop(), deck.pop() )

    之前的代码示例保证了在演示时可以产生特定的牌。

    上下文管理器的清理

    在本节中,将会讨论一些有关上下文管理器复杂的应用场景,当遇到异常时会尝试执行清理操作。

    这样可以解决一个常见的问题:希望想保存应用程序正在写的一个文件的副本。正如以下代码所示。

    with Updating( "some_file" ):
      with open( "some_file", "w" ) as target:
        process( target )

    目的是将原文件重命名为some_file copy并备份。如果上下文中一切正常——没有异常——然后删除备份文件或重命名为some_file old

    如果上下文没有正常工作——出现了异常——我们需要重命名新文件为some_file error并重命名旧文件为some_file,并把抛出异常前的原文件复制回去。

    将需要如下这样的上下文管理器。

    import os
    class Updating:
      def init( self, filename ):
        self.filename= filename
      def enter( self ):
        try:
          self.previous= self.filename+" copy"
          os.rename( self.filename, self.previous )
        except FileNotFoundError:
          # Never existed, no previous copy
          self.previous= None
      def exit( self, exc_type, exc_value, traceback ):
        if exc_type is not None:
          try:
            os.rename( self.filename, self.filename+ " error" )
          except FileNotFoundError:
            pass # Never even got created?
          if self.previous:
            os.rename( self.previous, self.filename )

    这个上下文管理器的enter()方法会试图对之前的文件进行备份(如果它已经存在)。如果它不存在,就不会有任何行为。

    exit()方法用来接收从上下文中抛出的异常信息。如果没有异常,将返回之前保存的文件,同时上下文中创建的文件也会被保留。如果出现异常,exit()方法将保存输出(使用“error”为后缀),为了之后的调试。它也会将异常前保存的文件复制回去。

    这个功能和try-except-finally语句是等价的。然而,它具备一个优势,分离了应用程序的相关处理和上下文的管理逻辑。应用程序处理在with语句内部中完成。而上下文的问题被放在另一个类中处理。