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语句的上下文中,并使用with的as语句为其赋值。
也可以考虑另一种方式达到同样的目的。可以在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语句内部中完成。而上下文的问题被放在另一个类中处理。
