看一个项目的源码的时候,看到了这句,class xxx(six.with_metaclass(ABCMeta, xxx)):
对其中的with_metaclass
不甚理解,学习一番后,原来是涉及到了python中的高级知识:元类,学习后遂作记录。
什么是元类
首先说,元类是创建类的类。python 中一切皆对象,那么类同样也是对象,可以说,类是其元类的实例对象。所有元类的祖宗是 type,只有 type 及其子类才能作为元类。要想看一个对象的元类,可以通过 type()
和 obj.__class__
查看:
如何创建类
既然类是其元类的实例对象,那么类的创建除了写 class
关键字,还可以通过 meta(classname,(parentclass,),{attr})
来实例化元类的方式创建。另外,如果一个类 A 由其元类 B 创建,类 C 继承类 A,那么类 C 也是由元类B创建。也就是说:某个类的元类为A,那么该类及其子类都是由元类A创建。而元类是继承 type
的,因此还可以通过 type(classname,(parentclass,),{attr})
创建。总结以上可得,类的三种创建方法:
- 通过
class
关键字创建 - 通过
meta(classname,(parentclass,),{attr})
创建 - 通过
type(classname,(parentclass,),{attr})
创建
其中元组()中传递的是该类继承的父类,字典 {} 传递的是类的属性或方法
以上可以看到 A,B,C 都是 class 类型。类A中通过 metaclass 指定了 MyType 是 A 的元类。
实例的构造过程
一个实例是怎么被类创建出来的呢?先说结论:
类到实例经历了2个过程:
- 执行类的 __new__ 方法,返回一个空的对象
- 执行类的 __init__ 方法,对对象进行参数的初始化,后返回该对象
因此可以说 __new__ 这个魔法方法是个构造方法,而__init__ 方法是个初始化方法。
另外看一下 __call__ 方法,一个对象要想是可调用的,必须实现 __call__ 方法,或者说实现了__call__ 方法的对象都是可调用的。如果一个对象中实现了__call__方法,那么这个实例就可以调用。
但是要注意调用的时机。第一个 A() 只是实例化对象的操作,a() 才算是对实例的调用。
知道了以上3个魔法方法以后,再看看类到实例经历了那些过程。
可以看到依次执行了 __new__ 方法,__init__ 方法和 __call__ 方法。其中,定义 class A 相当于实例化了 MyType,实例化A() 就相当于调用了 Mytype 实例(也就是类A),因此触发了 Mytype 的__call__方法。
如果 __new__ 方法,__init__ 方法不返回最终得到的类就是 NoneType
,可以去掉 return
重写 __new__ 方法,__init__ 方法自行验证。
with_metaclass是什么意思
先说结论:with_metaclass
是个函数,返回临时类,这个临时类由元类创建,同时又是新类的父类。
这么比较难懂,看代码。
首先看下 with_metaclass
的源码,做了些什么事情。
进一步地,代码可以简化为
以上执行 __new__ 实际就是对相应的类实例化操作。可以看到with_metaclass
函数最终就是返回了 metaclass 元类的实例(也就是类),并且名字叫 temporary_class,那么 metaclass 实例化是什么呢,也就是说 metaclass 执行 __new__ 返回什么呢?因此以上代码可以继续简化为
使用 with_metaclass
创建类的代码如下
如果将A 中调用 with_metaclass 的参数带入进去就是
可以看到,其实就是使用 Mytype 元类创建了一个临时类 temporary_class,A 继承了 temporary_class,根据某个类的元类为A,那么该类及其子类都是由元类A创建,那么类A实际上也是由 Mytype 创建。这就是 with_metaclass 的作用。
那么使用 metaclass 和 with_metaclass 有什么区别?本质上没有什么区别,只是写法上稍有不同。
元类有什么用
通常是对定义的类有特殊要求的时候会用到,常见于框架中。
比如要求类中的属性不能以 test 开头,就可以这样写。
最后,ABCMeta 是抽象元类,他继承 type ,如果元类不指明继承自ABCMeta,就会默认继承 type,创造的就不是抽象类了(抽象类是不能实例化的)。然后 type 是继承 object 的,object 真的是一切对象的大 boss。
看下源码,以证以上。
type 继承 object
或者