元类从概念上理解是不难的,仅从字面上去理解,当初学数据库的时候,就知道有元数据这么个东西,那么元数据是什么?元数据就是用来描述数据库的数据。那么一样的道理,类是什么?类是创建对象用的,那么元类就是创建类用的。
一,先来看type方法
class Lan(object):
def run(self):
print('run...')
l = Lan()
print(type(l))
print(type(Lan))
Output:
<class '__main__.Lan'>
<class 'type'>
可以看到,l是通过Lan创建的对象,这个对象的类型是Lan。而Lan这个类本身的类型是type。
于是就有想法,Lan这个类可不可以通过type来创建?事实上是可以的,来看实现
def func(self):
print('run...')
Lan = type('Lan', (object,), {'run':func})
l = Lan()
print(type(l))
print(type(Lan))
Output:
<class '__main__.Lan'>
<class 'type'>
这段通过type创建的代码实现了和前面代码一模一样的功能,事实上,一般情况下,python解释器在扫描到class的时候就是用type去创建它的。上面这段代码 不过是我们手动做了解析器的工作。
type的参数:
- class的名称;
- 继承的父类集合,注意Python支持多重继承,如果只有一个父类,别忘了tuple的单元素写法;
- class的方法名称与函数绑定,这里我们把函数
fn
绑定到方法名hello
上。
到这一步,其实最需要关注的是,我们既然动态的创建了一个Lan类 那么这个Lan类其实也是一个对象,type的对象。这也正是动态语言与静态语言最大的不同,可以在运行时动态生成类,因为它也是个对象嘛,但是静态语言如java就不行。可能你会说,java不是有反射嘛?但是请注意,反射也是根据.class文件去获取类对象,然后用这个类对象去实例化,去调用。类的定义在一早已经写好了。而python这里,是在运行前连类的定义都没有。所以注意区分。
二,元类metaclass
元类是什么开篇已经解释过,现在来看怎么用
class LanMetaclass(type):
def __new__(cls, name, bases, attrs):
attrs['fly'] = lambda self, value: print("%s can fly" % value)
return type.__new__(cls, name, bases, attrs)
class Lan(object, metaclass=LanMetaclass):
def run(self):
print('run...')
l = Lan()
l.fly('bird')
Output:
bird can fly
是不是特别奇怪? 在Lan中我们只定义了run方法,可是Lan的对象l却可以调用fly方法。这就是元类的用法,可以动态的修改类的定义。
分析一下是怎么做到的:
前面我们说过,Lan是由解释器调用type创建的,那么这里可以再深入一些,解释器在扫描Lan的时候,发现有metaclass这个字段,那么就去调用相应的类(这里是LanMetaclass)去创建类,也正是这个原因,让它需要继承type,最后实际还是交给了type去创建,我们不过是创建前,修改了类的定义。至于怎么修改,看了__new__的四个参数就明白了:
- 当前准备创建的类的对象;
- 类的名字;
- 类继承的父类集合;
- 类的属性集合。
和type几乎一模一样,不过多了一个参数而已,所以用attrs去修改即可。
打印四个参数,加深理解:
class LanMetaclass(type):
def __new__(cls, name, bases, attrs):
print(cls)
print(name)
print(bases)
print(attrs)
return type.__new__(cls, name, bases, attrs)
class Lan(object, metaclass=LanMetaclass):
def run(self):
print('run...')
l = Lan()
Output:
<class '__main__.LanMetaclass'>
Lan
(<class 'object'>,)
{'__module__': '__main__', '__qualname__': 'Lan', 'run': <function Lan.run at 0x0000015EEA761950>}
看了上面的解释,其实我对cls是什么还是有疑惑的,然后就去查阅了官网文档,下面我们来捋一捋思路。
三,__new__和__init__两个魔法方法
首先__init__是什么? __init__(self, 参数) 目的就是初始化绑定参数,self就代指的是当前的对象。
那么问题来了,self这个实例是哪里来的?__new__(cls,...) 就是用来创建self这个实例的。写一段代码来理解:
class Lan(object):
def __new__(cls):
print(cls)
return object.__new__(cls)
l = Lan()
Output:
<class '__main__.Lan'>
很明显,cls就是正在创建的Lan的对象,只不过这个具体的创建过程我们需要交给父类去完成,把这个类的信息cls提供给父类方法去调用它的__new__就可以了。
其实这真是很简单的概念,标题二里面之所以让我看得比较费解,是因为把这个和元类混到一起去理解了就变得不清晰了。事实上,哪怕是元类,也不见得有那么难,看过一篇文章有这样的说法:元类,其实不难,难就难在当你要用到元类的时候,你要处理的问题很难。
所以现在可以更好的理解元类的工作流程:
当我们创建的类有metaclass字段的时候,调用具体的Metaclass类,再它用__new__方法返回具体的实例之前,我们改变这个需要创建类的属性,然后调用type去动态的创建了一个新的class。
思想是不是非常简单,但是元类使用起来,就像上面说的,真的不简单,因为你往往会处理很复杂的事情。
另外参考网文转了一篇用metaclass写一个orm框架的最基本原理,点我去看