python的元类

元类从概念上理解是不难的,仅从字面上去理解,当初学数据库的时候,就知道有元数据这么个东西,那么元数据是什么?元数据就是用来描述数据库的数据。那么一样的道理,类是什么?类是创建对象用的,那么元类就是创建类用的。

一,先来看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的参数:

  1. class的名称;
  2. 继承的父类集合,注意Python支持多重继承,如果只有一个父类,别忘了tuple的单元素写法;
  3. 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__的四个参数就明白了:

  1. 当前准备创建的类的对象;
  2. 类的名字;
  3. 类继承的父类集合;
  4. 类的属性集合。

和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框架的最基本原理,点我去看

猜你喜欢

转载自blog.csdn.net/qq_21294095/article/details/85133852