21.1 类工厂函数
其实我们经常使用的 collections.namedtuple
就是一个类工厂函数,我们把类名和几个属性名传给这个函数,它就会创建一个 tuple 的子类。
1 | def record_factory(cls_name, field_names): |
通常,我们把 type 视作函数,因为我们像函数那样使用它,例如,调用 type(my_object)
获取对象所属的类——作用与 my_object.__class__
相同。然而,type
是一个类。当成类使用时,传入三个参数可以新建一个类:
1 | MyClass = type('MyClass', (MySuperClass, MyMixin), {'x': 42 'x2': lambda self : self.x * 2}) |
type
的三个参数分别是 name
、bases
和 dict
。最后一个参数是一个映射,指定新类的属性名和值。上述代码的作用与下述代码相同:
1 | class MyClass(MySUperClass, MyMixin): |
21.2 定制描述符的类装饰器
类装饰器与函数装饰器非常类似,是参数为类对象的函数,返回原来的类或修改后的类。
1 | def entity(cls): |
类装饰器有个重大缺点: 只对直接依附的类有效。这意味着,被装饰的类的子类可能继承也可能不继承装饰器所做的改动,具体情况视改动的方式而定。
21.3 导入时和运行时比较
导入模块时,解释器会执行顶层的 def 语句,可是这么做有什么作用呢?解释器会编译函数的定义体(首次导入模块时),把函数对象绑定到对应的全局名称上,但是显然解释器不会执行函数的定义体。
对类来说,情况就不同了:在导入时,解释器会执行每个类的定义体,甚至会执行嵌套类的定义体。执行类定义体的结果是,定义了类的属性和方法,并构建了类对象。
21.4 元类基础知识
元类是制造类的工厂,不过不是函数,而是类。
根据 Python 的对象模型,类是对象,因此类肯定是另外某个类的实例。
1 | In [1]: 'spam'.__class__ |
为了避免无限回溯,type
是其自身的实例,如最后一行所示。
除了 type
,标准库中还有一些别的元类,例如 ABCMeta
。
1 | In [1]: import collections |
向上追溯,ABCMeta
最终所属的类也是 type
。所有类都直接或间接地是 type
的实例,不过只有元类同时也是 type
的子类。若想理解元类,一定要知道这种关系:元类(如 ABCMeta
)从 type
类继承了构建类的能力。
我们要抓住的重点是,所有类都是 type
的实例,但是元类还是 type
的子类,因此可以作为制造类的工厂。具体来说,元类可以通过实现 __init__
方法定制实例。元类的 __init__
方法可以做到类装饰器能做的任何事情,但是作用更大。元类的 __init__
方法有四个参数:
self
:要初始化的类对象(一般改名为cls
)name
:类名bases
:基类列表dic
:类的属性名和值
21.5 定制描述符的元类
1 | class EntityMeta(type): |
因为元类会影响使用期作为元类的类及所有子类的初始化,所以我们可以在元类中定义一些行为,这样就能够作用在整个类继承链条的类上。
21.6 元类的特殊方法 __prepare__
在某些应用中,可能需要知道类的属性定义的顺序。如前所述,type
构造方法及元类的 __new__
和 __init__
方法都会收到要计算的类的定义体,形式是名称到属性的映像。然而在默认情况下,那个映射是字典;也就是说,元类或类装饰器获得映射时,属性在类定义体中的顺序已经丢失了。
这个问题的解决办法是,使用 Python 3 引入的特殊方法 __prepare__
。 这个特殊方法只在元类中有用,而且必须声明为类方法(即要使用 @classmethod
装饰器定义)。解释器调用元类的 __new__
方法之前会先调用 __prepare__
方法,使用类定义体中的属性创建映射。
__prepare__
方法的第一个参数是元类,随后两个参数分别是要构建的类的名称和基类组成的元组,返回值必须是映射。元类构建新类时,__prepare__
方法返回的映射会传给 __new__
方法的最后一个参数,然后再传给 __init__
方法。
1 | import collections |
在上述例子中,由于 __prepare__
方法返回的是一个有序的字典,所以在 __init__
中的 for 循环里我们能够根据属性定义的顺序来进行遍历。
在现实世界中,框架和库会使用元类协助程序员执行很多任务,例如:
- 验证属性
- 一次把装饰器依附到多个方法上
- 序列化对象或转换数据
- 对象关系映射
- 基于对象的持久存储
- 动态转换使用其他语言编写的类结构
21.7 类作为对象
cls.__bases__
:由类的基类组成的元组。cls.__qualname__
:Python 3.3 新引入的属性,其值是类或函数的限定名称,即从模块的全局作用域到类的点分路径。cls.__subclasses__()
:这个方法返回一个列表,包含类的直接子类。cls.mro()
:构建类时,如果需要获取储存在类属性 __mro__
中的超类元组,解释器会调用这个方法。