深入理解python元类

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/wenzhou1219/article/details/83931028

相对python的简单来说,元类显得有些复杂了,但是理解元类会让你的python代码威力大大增加,理解元类需要了解一些python背后的东西,相信通过本文的叙述你能很快理解运用这一利器。

类的本质

首先我们思考下,python中的类是什么,如下代码:

class ClassObject():
    class_a = 1

    def __init__(self):
        self.object_a = 2

    def func(self):
        pass

if __name__ == '__main__':
    print(ClassObject, id(ClassObject))

    a = ClassObject()
    print(a, id(a))

    print(ClassObject.class_a, a.class_a, a.object_a)

输出如下:

(<class __main__.ClassObject at 0x000000000326C948>, 52873544L)
(<__main__.ClassObject instance at 0x0000000003482108>, 55058696L)
(1, 1, 2)

id用来输出对应对象地址,可以看到这里类其实也是一种对象,类实例由这个对象来创建,我们把这种结构画成图如下:

在导入类时,内存中创建名字为ClassObject的对象,这个对象内存中的结构包括类属性class_a和方法func,当我们使用a=ClassObject()时,python在名字空间中找到这个ClassObject对象,然后使用这个对象类创建类实例a,其中a对象内存中结构包括对象属性object_a并指向上一级空间。

当使用print打印对应属性时,在各自的内存空间中找到属性并输出值,对于对象a来说自身内存结构中找不到class_a,因此到上一级ClassObject空间中去找class_a。

总之,我们要明白,类本身在内存中就是一种对象结构。

元类的定义

既然,类是一种对象,那么是谁来创建类这种对象呢。

自然是我们的元类(metaclass),所谓元就是根本、本源的意思(比如我们常说的元始天尊,哈哈)

元类需要继承type,一个典型的元类定义如下:

class MetaClass(type):
    def __new__(cls, *args, **kwargs):
        return type('MetaClassNew', (), {"id":1})

if __name__ == '__main__':
    print(MetaClass)
    print(MetaClass())

    b = MetaClass()()
    print(b, b.id)

输出如下:

<class '__main__.MetaClass'>
<class '__main__.MetaClassNew'>
(<__main__.MetaClassNew object at 0x00000000032EF470>, 1)

第一行输出表明就像之前说的,元类MetaClass也是一种对象,

第二行实例化MetaClass()创建的是类MetaClassNew,

b = MetaClass()()表示先实例化出类MetaClassNew,然后实例化类MetaClassNew得到MetaClassNew的对象。

type是一切元类的基类,通过它来完成类的产生,type('MetaClassNew', (), {"id":1})分别传入类名、基类元组和类属性字典即可完成类的动态生产。

因此可以看到,元类就是类的类,你要是愿意,可以定义元类的元类的元类...,当然一般来说这没有意义,但是却说明了类和元类的关系。

元类的应用

那么我们能用元类来做什么呢?其实元类对应c++/java中的模板,这些语言中也有模板元编程的概念,所谓元编程,顾名思义就是修改根源,即对产生类的过程进行定制,在这些编译语言中运用模板来做编译时期的类型推断进而实现泛型和一些编译期的计算。

在python中类似,利用元类可以修改类的产生,对类的属性进行定制,在类产生时进行一些操作,比如可以对类的类属性进行校验,动态注册相关类等。总之就是拦截类的创建,做一些自定义Hook操作

下面来看两个常见的应用

1.类参数的校验

实际应用中,对于类来说,如果参数错误,往往要我们在程序运行时才会动态判断检测,但是对于类本身的一些属性在类导入定义完成时就应该检测出来。

比如,我们定义一个多边形类,有一个固定属性表示多边形边数,如果定义类时指定边数<3,那显然定义就是不合法的。

如下,我们定义一个元类,在类创建先做校验:

class ValidatePolygon(type):
    def __new__(meta, name, bases, class_dict):

        print(meta, name, bases, class_dict)
        if bases!=(type(object),):
            if class_dict['sides']<3:
                raise ValueError('Polygon needs 3+ sides')

        return type.__new__(meta, name, bases, class_dict)

下面是多边形定义,在2.X中通过__metaclass__类指定元类负责hook类的创建过程,在如上的hook中校验多边形的边数。

print('Before class')
class Line(object):
    __metaclass__ = ValidatePolygon

    print('Before sides')
    sides = 1
    print('After sides')

    print('After class')

导入类时输出如下:

Before class
Before sides
After sides
After class
...
ValueError: Polygon needs 3+ sides

可以看到,在类导入完成后,校验当前多边形类的属性不满足条件。

2.类的自注册

实际应用中,经常我们需要序列化和反序列化类,序列化很简单,类按照一定结构存起来就行,关键是反序列化——字符串形式保存的对象如何映射到已有的类的对象,从而反序列化出对应的类实例

最简单的方法,我们维护一个map,每当有一个新类的时候,在map中新增一条映射记录。这样可以解决问题,但是如果开发人员忘记新增映射记录,那么就会造成反序列化失败的大问题。

如果能够在定义新类的时候自动在map中新增记录就可以解决这个问题,这正是元类可以做的,hook类的创建并在此注册新类,演示如下:

register_map = {}
def register_class(cls):
    print('---Register class:%s---' %cls.__name__)
    register_map[cls.__name__] = cls

class Meta(type):
    def __new__(cls, name, bases, class_dict):
        cls = type.__new__(cls, name, bases, class_dict)
        register_class(cls)
        return cls

class SelfRegisterSerializer(object):
    __metaclass__ = Meta

    def __init__(self, *args):
        self.args = args

    def serialize(self):
        return json.dumps({'class':self.__class__.__name__,
                           'args':self.args})

    @staticmethod
    def dserialize(data):
        params = json.loads(data)
        name = params['class']
        target_class = register_map[name]
        return target_class(*params['args'])

可以看到,导入类完成后,register_class名字映射到对应的类对象。 这样当反序列化时target_class = register_map[name],

通过查找对应名字的类对象来创建类实例即可。这样这个类序列化和反序列化就非常简单了,如下:

class MyClass1(SelfRegisterSerializer):
    pass

class MyClass2(SelfRegisterSerializer):
    pass

if __name__ == '__main__':
    a1 = MyClass1(1,2,3)
    print('Before:', a1)

    data = a1.serialize()
    print('Serialize:', data)

    c1 = SelfRegisterSerializer.dserialize(data)
    print('Dserialize:', c1)

这里正常定义继承类即可,导入类时会自动注册类,程序结构非常简单。

这个实例可以扩展到很多应用场景,如IOC类创建,插件动态加载等待,总之就是hook类创建时干自己的活。

演示代码下载链接

原创,转载请注明来自http://blog.csdn.net/wenzhou1219 

猜你喜欢

转载自blog.csdn.net/wenzhou1219/article/details/83931028