什么是metaclass?
metaclass一般翻译成“元类”,但也有人认为应该更关注元类“超越变形”的能力,应该翻译成超越类之类的。但是个人觉得“元类”比较顺口,其“本元”的含义也是实打实存在的,故本文用元类作为metaclass的中文翻译。
metaclass(元类)可以理解成是类的抽象、本元。类元编程即使用自定义metaclass获取极强灵活性的一种编程方式。
不过,在多处都有提到的需要注意的是:如果不是开发框架,不要使用元类,除非只是为了寻找乐趣:)。
什么是本元?
首先还是要搞清楚什么是本元?这个概念很抽象,必须弄清楚。
本元可以理解成一种更高程度的抽象。首先,类(class)是对象(object)的抽象,对象是类的实例。因此可以说类是对象的本元。而在python中,所有类都是type
类的实例,也就是说type
是所有类的抽象,所以你可以理解type
就是那个元类。
type
一般我们会常用type(obj)
来获取obj
对象的类型。但type
也可以用来创建新类型。这时需要传入三个参数:类名、父类tuple、属性字典。
class MyClass: ...
等价于MyClass = type(classname, superclasses, attributedict)
1 | class SuperCls: |
上面类对象的mro()
方法代表Method Resolution Order,即方法解析顺序,可以看到这个类的继承顺序。
通过上面的代码可以看出,使用type
来动态创建类和你自己写代码创建一个类是完全一样的。
还有个问题,当我们使用type
的时候,它自己是个type
类还是type
对象?如果你直接在解释器中输入print(type)
会得到返回<class 'type'>
,因此可以认为你使用的就是type
类。
MetaClass
OK,搞清楚了什么是“本元”和type
,我们再来看metaclass
。
1 | # 自定义metaclass |
看,我们的元类继承自type
类。你在使用这个元类去定义一个新的类时,会调用元类的__new__
和__init__
;当你用这个新类去实例化一个新对象时又会调用元类的__new__
。
因此,你可以可以重载type
的这三个方法来修改type
的行为,但要理清这几个方法被调用的时机:
1 | __init__(cls, what, bases=None, dict=None) |
典型案例
来看几个类元编程的典型案例。
Singleton单例模式
1 | class SingletonMeta(type): |
注:代码来自 https://github.com/Meteorix/python-design-patterns 。
这里重载的是type
的__call__
方法。改变了用户类实例化的行为,使其只能得到唯一的实例。
简易ORM框架
1 | class Field(object): |
注:代码来自 https://www.liaoxuefeng.com/wiki/1016959663602400/1017592449371072
这里重载的是type
的__new__
方法,因此改变的是用户类在定义时的行为。这里用户类是一个数据模型,元类编程做的是在用户定义一个数据模型类的时候,把所有是Field
类型的属性收集起来,统一放到mapping
属性中。这样基类的方法save
可以直接通过mapping
属性来保存需要保存的Field
属性们。
YAMLObject
1 | class YAMLObjectMetaclass(type): |
这里重载的是type
的__init__
方法,因此改变的是用户类在定义时的行为。在定义了用户类Monster
时,便通过cls.yaml_loader.add_constructor(cls.yaml_tag, cls.from_yaml)
将Monster
类注册到系统。
PyText
1 | class ComponentMeta(type): |
这里重载的是type
的__new__
方法,因此改变的是用户类在定义时的行为。在定义了用户组件类时,便通过Registry.add(component_type, new_cls, new_cls.Config)
将该组件类注册到系统。