因为 oschina 的博客不支持 mermaid 的图,能够看看挂在gitee上的静态博客html
元类是 python 里被说烂了的一个东西,然而平常用到的地方实在很少,每次想到都得查一下谷歌,想一想干脆在博客留个笔记好了。python
元类的主要用途是定制类的产生过程,以便于根据类声明包含的信息来建立出不一样的类。git
提到元类不得不说一下 python 的类型系统。app
python 的 class 也被视做一个对象,定制一个 class 的构造过程其实就和平时在 class 定义里写__init__
没啥区别。函数
python3 里类的类型是type
,type
又继承自object
,object
的父类是本身,构成一个奇怪的闭环。其中,type
自己是一个特殊的类,他是本身的实例。spa
graph TB; type --> |inherite|object; type --> |instance-of| type; object --> |instance-of|type; other-cls --> |instance-of| type; other-cls --> |inherite| object; other-cls-instance --> |instance-of|other-cls;
type
有两种调用方式,一种是最经常使用的接受一个对象参数,返回该对象的类型,另外一种是不怎么经常使用的,直接建立一个新的类型。翻译
# usage with one argument type(object) # 返回对象的类型,这里返回的是 `type` # usage with three arguments type(name, bases, attr) # 返回新建立的类型
元类语法以下code
class MyClass(basecls1, basecls2, metaclass=MetaClass, named1=arg, named2=arg): ...
通常的元类能够是一个真正的class
或者一个函数。htm
以函数为例:对象
def meta_f(name, bases, attr): return type(name, bases, attr) class A(metaclass=meta_f): ...
以类为例:
class MetaC(type): def __new__(mcs, name, bases, attr): return type.__new__(mcs, name, bases, attr) class A(metaclass=MetaC): ...
元类能够接受参数,参数必须是命名的,传递参数的方式是写在类声明的继承列表里。
def meta(name, bases, attr, named_arg, optional_arg=None): return type(name, bases, dict(**attr, arg=named_arg, option=optional_arg)) class A(metaclass=meta, named_arg="hi"): ... print(A.arg) # output: hi
位置参数都会被当成继承列表,做为bases
参数(list)的一部分传入元类。
有了元类那么就有了相应继承规则,显而易见。元类用于构造一个类,两个父类分别有一个不一样的元类显然会形成冲突:这个子类用哪一个元类构造?
首先看元类的在建立类的过程当中的位置,摘自 python 文档3.3.3.1. Metaclasses
- MRO entries are resolved
- the appropriate metaclass is determined
- the class namespace is prepared
- the class body is executed
- the class object is created
一旦处理完继承链(mro, method resolve order)以后,就会决定采用哪一个 metaclass 做为构造这个类的元类。
在 python 文档的3.3.3.3 determining the appropriate metaclass中描述了如何肯定合适的元类,摘录以下。
- if no bases and no explicit metaclass are given, then type() is used
- if an explicit metaclass is given and it is not an instance of type(), then it is used directly as the metaclass
- if an instance of type() is given as the explicit metaclass, or bases are defined, then the most derived metaclass is used
翻译以下
type()
将做为元类使用。有一个比较难理解的点是
most derived metaclass
也就是所谓的最衍生的元类。惯例,先放文档解释
The most derived metaclass is selected from the explicitly specified metaclass (if any) and the metaclasses (i.e. type(cls)) of all specified base classes. The most derived metaclass is one which is a subtype of all of these candidate metaclasses. If none of the candidate metaclasses meets that criterion, then the class definition will fail with TypeError.
简单翻译以下
最衍生的元类会从类声明中明确提供的元类,还有全部明确继承的基类的元类中选择。最衍生的元类是以上全部候选元类的子类型,若是没有类型符合这一条件,则抛出
TypeError
异常。
重点在于,最衍生的元类必须是,全部继承的基类的元类和指定元类的子类型。
在这里提醒一下,issubclass(cls, cls)
的结果是True
。换句话说,必须有一个类是全部元类的子类,或者全部基类有相同的元类。
代码举例以下
class MetaA(type): def __new__(mcs, name, bases, attr): print('MetaA <- '+name) return type.__new__(mcs, name, bases, attr) class MetaB(type): def __new__(mcs, name, bases, attr): print('MetaB <- '+name) return type.__new__(mcs, name, bases, attr) class BaseA: ... class BaseB(metaclass=MetaA): ... class BaseC(metaclass=MetaB): ... # 未指定元类,基类元类分别是type和type的子类,则选择继承链底部的那个类 class A(BaseA, BaseB): ... # Ok,元类是 MetaA # 指定元类,元类和基类元类相同的状况下,元类就是那个元类 class C(BaseB, metaclass=MetaA): ... # Ok,元类是 MetaA # 指定元类,元类并不处于继承链底端的状况下,元类选择继承链底端的类 class D(BaseB, metaclass=type): ... # Ok,元类是 MetaA # 指定元类,但元类和父类无父子类关系 class E(BaseC, metaclass=MetaA): ... # TypeError # 不指定元类,基类具备不一样的元类 class F(BaseA,BaseB,BaseC): ... # TypeError
输出以下
MetaA <- A MetaA <- C MetaA <- D In [71]: class E(BaseC, metaclass=MetaA): ... # TypeError --------------------------------------------------------------------------- TypeError Traceback (most recent call last) <ipython-input-71-9129a36c52b2> in <module> ----> 1 class E(BaseC, metaclass=MetaA): ... # TypeError TypeError: metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases In [72]: class F(BaseA,BaseB,BaseC): ... # TypeError --------------------------------------------------------------------------- TypeError Traceback (most recent call last) <ipython-input-72-1c510edd69d1> in <module> ----> 1 class F(BaseA,BaseB,BaseC): ... # TypeError TypeError: metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases
但元类是函数的状况下会有比较特殊的表现,注意规则二。
- 若是指定了元类,而且该元类不是 type 的实例,那么直接使用这个元类。
若是函数形式的元类做为父类的元类时不会列入选择,除非指定当前类的元类为函数,才会调用函数形式的元类,并且是无条件选择这个函数形式的元类。
def MetaA(name, bases, attr): print("MetaA <- "+name) return type(name, bases, attr) class MetaB(type): def __new__(mcs, name, bases, attr): return type.__new__(mcs, name, bases, attr) class A(MetaB, metaclass=MetaA): ... # Ok,无条件选择元类 MetaA