8.6 创建方法函数装饰器
一个类中方法函数的装饰器和一个单独的函数的装饰器是一样的,只是在不同的上下文中使用。这种上下文所带来的一个轻微的后果是必须经常显式地声明self变量。
方法函数装饰器的一个应用是追踪对象状态的改变。商业应用程序经常会创建有状态的记录;通常,这些记录会作为关系型数据库中的行。我们会在第9章“序列化和保存——JSON、YAML、Pickle、CSV和XML”、第10章“用Shelve保存和获取对象”和第11章“用SQLite保存和获取对象”中详细讲解对象的表示。
| 当我们有一些有状态的记录时,状态的改变应该是可以被追踪的。通过追踪可以确认改变已经被保存到记录中。为了能够追踪这些记录,必须可以用某种方式来获得每条记录在改变之前和改变之后的版本。状态数据库记录是一种长期使用的传统方法,但是不是任何情况下都必需的。不可变的数据库记录是另外一种可选择的设计。 |
当设计一个有状态的类时,任何setter方法都会带来状态的改变。这些setter方法通常会用@property装饰器,这样它们就可以被当作简单的属性来使用。如果我们这么做,我们可以添加一个@audit装饰器,这样就能合理地追踪所有的改变。我们会通过logging模块创建追踪日志。我们会用repr()方法函数生成一个可以用来浏览所有修改的完整文本,下面是一个追踪装饰器。
def audit( method ):
@functools.wraps(method)
def wrapper( self, args, **kw ):
audit_log= logging.getLogger( 'audit' )
before= repr(self)
try:
result= method( self, args, **kw )
after= repr(self)
except Exception as e:
auditlog.exception(
'%s before %s\n after %s', method.qualname, before, after )
raise
auditlog.info(
'%s before %s\n after %s', method.__qualname, before, after )
return result
return wrapper
我们创建了一个对象被修改前的文本表示。然后,调用原始的方法函数。如果有异常,会生成一个包含异常信息的追踪日志。否则,会在日志中生成一个INFO条目,它包含方法的全名、修改前的文本表示和修改后的文本表示。下面是一个修改过的Hand类,它会向我们展示要如何使用这个装饰器。
class Hand:
def init( self, *cards ):
self.cards = list(cards)
@audit
def iadd( self, card ):
self.cards.append( card )
return self
def repr( self ):
cards= ", ".join( map(str,self.cards) )
return "{class.name}({cards})".format(_
class=self.__class, cards=cards)
这个定义修改了iadd()方法函数,这样添加一张牌就成为一个可追踪的事件。这个装饰器会执行追踪操作,在进行操作前和完成操作后会保存Hand的文本表示。
方法装饰器的这种使用方式相当于对某个方法函数进行正式声明,这样会大量地修改状态。我们可以直接使用代码审查,确保所有符合要求的方法函数都像这个一样,被标记为是可追踪的。有一个没有解决的问题是追踪对象的创建过程。目前尚不清楚是否需要追踪对象的创建过程。有一些人会说对象的创建并不是一种状态改变。
在我们想要追踪对象创建的情境中,我们不可以在init()方法函数中使用这个audit装饰器。因为在执行init()之前什么都没有。我们可以通过以下两种方式补救这个问题。
- 可以添加一个new()方法用于确保一个空的__cards属性会以空集合的方法存在于类中。
- 可以修改audit()装饰器,当init()被执行时,接受程序抛出的AttributeError异常。
第2种方式相对来说更加灵活一些,我们可以像下面这样做。
try:
before= repr(self)
except AttributeError as e:
before= repr(e)
这段代码会记录类似AttributeError: 'Hand' object has no attribute '_cards'这样的信息作为初始化前的状态。
