Python 有一个不太为人所知的语法特性,就是类装饰器。其语法和工作方式都与第 2 章介绍的函数装饰器完全相同。唯一的区别在于它的返回值是一个类,而不是函数对象。 下面是一个类装饰器的例子,修改__repr__()方法并返回缩短的可打印对象表示,缩短后 的长度可任意取值,如下所示:

def short_repr(cls):

cls.__repr__ = lambda self: super(cls, self).__repr__()[:8] return cls

@short_repr

class ClassWithRelativelyLongName: pass

你将会看到以下输出:

   >>> ClassWithRelativelyLongName()

   <ClassWi

 当然,上面的代码片段并不是很好的代码示例,因为其含义过于模糊。不过,它展示

了本章介绍的多种语言特性可以综合使用。

• 在运行时不仅可以修改实例,还可以修改类对象。

• 函数也是描述符,之所以也可以在运行时添加到类中,是因为根据描述符协议,在

属性查找时将执行实际绑定的实例。

• 只要提供了正确的参数,super()调用可以在类定义作用域之外使用。

• 最后,类装饰器可以用于类的定义。 编写函数装饰器的其他内容也适用于类装饰器。最重要的是,它可以使用闭包,也可

以被参数化。利用这一点,可以将上一个例子重写成更加易于阅读和维护的形式:

def parametrized_short_repr(max_width=8): """缩短表示的参数化装饰器"""

def parametrized(cls):

"""内部包装函数,是实际的装饰器""" class ShortlyRepresented(cls): """提供装饰器行为的子类"""

def __repr__(self):

return super().__repr__()[:max_width]

              return ShortlyRepresented

          return parametrized

在类装饰器中这样使用闭包的主要缺点是,生成的对象不再是被装饰的类的实例,而是在 装饰器函数中动态创建的子类的实例。这会影响类的__name__和__doc__等属性,如下所示:

@parametrized_short_repr(10)

class ClassWithLittleBitLongerLongName:

pass

 类装饰器的这种用法会使类的元数据发生以下变化:

>>> ClassWithLittleBitLongerLongName().__class__ <class 'ShortlyRepresented'>

>>> ClassWithLittleBitLongerLongName().__doc__ 'Subclass that provides decorated behavior'


不幸的是,这个问题不能用第 2 章“(4)保存内省的装饰器”一节介绍的方法(使用额外 的 wraps 装饰器)简单解决。这样的话,在某些情况下以这种形式使用类装饰器会受到限制。 如果没有做其他工作来保存旧类的元数据,那么这可能会破坏许多自动生成文档工具的结果。

虽然有这样的警告,但类装饰器仍然是对流行的混入(mixin)类模式的一种简单又轻 量级的替代方案。

Python 中的混入类是一种不应被初始化的类,而是用来向其他现有类提供某种可复用 的 API 或功能。混入类几乎总是使用多重继承来添加,其形式如下:

      class SomeConcreteClass(MixinClass, SomeBaseClass):

          pass

混入类是很有用的设计模式,在许多库中都有应用。举个例子,Django 就是大量使用 这种模式的框架之一。虽然混入类很有用也很流行,但如果设计不好的话可能会导致一些 麻烦,因为大部分情况下都需要开发人员依赖多重继承。我们前面说过,由于 MRO 的存 在,Python 对多重继承的处理相对较好。但如果仅因为不需要额外工作且使代码变得简单 的话,最好避免将多个类子类化。这也是类装饰器能很好地代替混入类的原因。