8.7 创建类装饰器
和装饰一个函数类似,也可以写一个类装饰器,用来向类中添加功能。基本的原则都是一致的。装饰器是一个函数(或者一个可调用对象)。它接受一个类作为参数,返回一个类作为返回值。
在类定义中,我们的切入点很有限。大多数情况下,类装饰器会将额外的功能包裹进类中。从技术上来说,创建一个封装了原始类的类是可以的。但这种做法有一定的难度,因为包装类本身必须是非常通用的。也可以创建一个被装饰类的子类。但是这样的做法可能会导致装饰器的使用者非常困惑。另外一种最糟糕的做法就是从类中删除一些功能。
前面展示了一个很复杂的类装饰器。Functools.Total_Ordering装饰器向类中注入一系列新的方法函数。这种实现方法用的技术是创建lambda对象并且将它们赋值给类的属性。
接下来,我们会介绍一些相对简单的装饰器。在调试和记录日志时,创建针对类的日志记录器可能会有一个小问题。通常,我们希望每个类都有自己的日志记录器。我们经常被强制要求像下面这样做。
class UglyClass1:
def init( self ):
self.logger= logging.getLogger(self.class.qualname)
self.logger.info( "New thing" )
def method( self, *args ):
self.logger.info( "method %r", args )
这个类的缺点是它创建了一个对象,它不属于类操作的一部分却是类的一个独立方面的logger实例。我们不希望用这种额外的方面污染我们的类。但是这并不是问题的全部。虽然logging.getLogger()非常高效,但是并非完全没有代价。我们希望每次创建UglyClass1实例时,可以避免这种额外的消耗。
下面是一个稍微好一些的版本。我们将logger从类中每个独立的对象中分离出来,把它变成了一个类级的实例变量。
class UglyClass2:
logger= logging.getLogger("UglyClass2")
def init( self ):
self.logger.info( "New thing" )
def method( self, *args ):
self.logger.info( "method %r", args )
这个类的优点是它只调用了logging.getLogger()一次。但是,它有严重的DRY问题。在定义中,没有办法自动设置类名。由于类还没有被创建,因此必须重复类名。可以用下面的这个小装饰器来解决DRY问题。
def logged( class ):
class.logger= logging.getLogger( class.qualname )
return class
这个装饰器修改了类的定义,它添加了logger引用作为类级属性。现在,每个方法都能用self.logger来生成追踪或者调试信息。当我们想要使用这个功能时,可以在类上应用@logged装饰器。下面的SomeClass是一个使用了@logged装饰器的例子。
@logged
class SomeClass:
def init( self ):
self.logger.info( "New thing" )
def method( self, *args ):
self.logger.info( "method %r", args )
现在,我们类中包含了一个每个方法都可以使用的logger属性。logger的值不是对象的一个功能,这样就保证了这个方面和类的其他方面是分离的。这个属性还有一个额外的好处就是,它在导入模块时创建了logger实例,减少了一些日志记录的开销。可以将这个类与UglyClass1进行比较,后者在每次创建实例时都会调用logging.getLogger()。
