详解python metaclass(元类)

    元编程,一个听起来特别酷的词,强大的Lisp在这方面是好手,对于Python,尽管没有完善的元编程范式,一些天才的开发者还是创作了很多元编程的魔法。Django的ORM就是元编程的一个很好的例子。

    本篇的概念和例子皆在Python3.6环境下

    一切都是对象

    Python里一切都是对象(object),基本数据类型,如数字,字串,函数都是对象。对象可以由类(class)进行创建。既然一切都是对象,那么类是对象吗?

    是的,类也是对象,那么又是谁创造了类呢?答案也很简单,也是类,一个能创作类的类,就像上帝一样,开启了万物之始。这样的类,称之为元类(classmeta)。

    类的定义

    对象是通过类创建的,这个很好理解。例如下面的代码:

    
    class Bar(object):
      pass
    
    bar = Bar()
    print(bar, bar.__class__)  # <__main__.Bar object at 0x101eb4630> <class '__main__.Bar'>
    print(Bar, Bar.__class__) # <class '__main__.Bar'> <class 'type'>
    

    可以看见对象 bar 是类 Bar 创建的实例。然而 Bar,看起来却是由一个叫 type 的类创建的实例。即 bar <-- Bar < -- type

    上面的例子,对象是动态创建的,类则是通过关键字 class 声明定义的。class关键字背后的玄机是什么呢?

    实际上,class Bar(object) 这样的代码,等价于 Bar = type('Bar', (objects, ), {})
    即类 type 通过实例化创建了它的对象 Bar,而这个 Bar 恰恰是一个类。这样能创建类的类,就是 Python 的元类。

    从创建 Bar 的代码上来看,元类 type 的 __init__ 方法有3个参数,

    • 第一个是创建的类的名字
    • 第二个是其继承父类的元类列表,
    • 最后就是一个属性字典,即该类所具有的属性。

    type 元类

    type是小写,因而很容易误以为它是一个函数。通过help(type)可以看到它的定义如下:

    
    class type(object):
      """
      type(object_or_name, bases, dict)
      type(object) -> the object's type
      type(name, bases, dict) -> a new type
      """
      def __init__(cls, what, bases=None, dict=None): # known special case of type.__init__
        """
        type(object_or_name, bases, dict)
        type(object) -> the object's type
        type(name, bases, dict) -> a new type
        # (copied from class doc)
        """
        pass
    
       @staticmethod # known case of __new__
      def __new__(*args, **kwargs): # real signature unknown
        """ Create and return a new object. See help(type) for accurate signature. """
        pass
    

    如前所述,__init__方法接受三个参数,type 实例化的过程,会创建一个新的类。创建类的代码来自 __new__ 方法,它的参数其实和 __init__,一样。至于它们之间有什么关系,后面再做介绍。目前只要知道,当调用 type 进行实例化的时候,会先自动调用 __new__ 方法,然后再接着调用 __init__方法,在类外面来看,最终会实例化一个对象,这个对象是一个类。

    从 type 的定义来看,它继承 object,Python3的所有类,都继承来着 object,类type 也是 object 的实例,令人奇怪的是,object 既是类也是对象,它也是由 type实例化而来。有一种鸡生蛋,蛋生鸡的悖论。暂且先不管,只要知道所有类的顶级继承来自 object 就好。

    自定义元类

    既然元类可以创建类,那么自定义元类就很简单了,直接继承类 type 即可。先看下面一个例子:

    
    class MyType(type):
      pass
    
    
    class Bar(object, metaclass=MyType):
      pass
    
    
    print(MyType, MyType.__class__) # <class '__main__.MyType'> <class 'type'>
    print(Bar, Bar.__class__) # <class '__main__.Bar'> <class '__main__.MyType'>
    

    可以看到,Bar在声明的时候,指定了其元类,此时的类 Bar 的__class__属性不再是 type,而是 MyType。即之前定义 Bar 的代码不再是 Bar = type('Bar', (objects, ), {}), 而是 Bar = MyType('Bar', (objects, ), {})。创建的元类的代码是MyType = type('MyType', (objects, ), {})

    如果一个类没有显示的指定其元类,那么会沿着继承链寻找父类的元类,如果一直找不到,那么就使用默认的 type 元类。

    元类冲突

    每个类都可以指定元类,但是父类和子类的元类要是一条继承关系上的,否则会出现元类冲突。并且这个继承关系中,以继承最后面的元类为其元类。

    元类的查找顺序大致为,先查看其继承的父类,找到父类的元类即停止。若直接父类没有元类,直到顶级父类 object ,此时父类(object)的元类是 type(basemetaclass),再看其自身有没有指定元类(submetaclass),如果指定了元类(submetaclass),再对比这个子元类(submetaclass)和父元类(basemetaclass),如果它们毫无继承关系,那么将会抛出元类冲突的错误。如果指定的子元类是父元类的父类,那么将会使用父元类,否则将使用期指定的子元类。

    submetaclass <- basemetaclass使用 submetaclass 作为最终元类,
    basemetaclass <- submetaclass, 使用 basemetaclass 作为最终元类,
    两者无继承关系,抛出冲突。

    有点像绕口令,且看代码例子

    
    class MyType(type):
      pass
    
    # 等价于 MyType = type('MyType', (object, ), {})
    
    class Bar(object, metaclass=MyType):
      pass
    
    # 等价于 Bar = MyType('Bar', (object, ), {})
    
    class Foo(Bar):
      pass
    
    # 等价于 Foo = MyType('Foo', (Foo, object, ), {})
    
    print(Bar, Bar.__class__)  # <class '__main__.Bar'> <class '__main__.MyType'>
    print(Foo, Foo.__class__) # <class '__main__.Foo'> <class '__main__.MyType'>
    

    Bar的父元类(basemetaclass)type,指定子元类(submetaclass)是 MyType, MyType 继承自 type,所以Bar的元类是 MyType。

    又如:

    
    class MyType(type):
      pass
    
    
    class Bar(object, metaclass=MyType):
      pass
    
    
    class Foo(Bar, metaclass=type):
      pass
    
    
    print(Bar, Bar.__class__)  # <class '__main__.Bar'> <class '__main__.MyType'>
    print(Foo, Foo.__class__) # <class '__main__.Foo'> <class '__main__.MyType'>
    

    尽管 Foo 也指定了元类(submetaclass) type,可是其父类的元类(basemetaclass)是 MyType, MyType
    是 type的子类,因此 Foo的元类抛弃了指定的(submetaclass) type,而是沿用了其父类的MyType。

    当 submetaclass 和 basemetaclass 没有继承关系的时候,将会元类冲突

    
    class MyType(type):
      pass
    
    class MyOtherType(type):
      pass
    
    class Bar(object, metaclass=MyType):
      pass
    
    
    class Foo(Bar, metaclass=MyOtherType):
      pass
    

    运行代码,当定义的时候就会出现TypeError: metaclass conflict: the metaclass of a derived class must be a (non-strict)元类冲突的错误。

    修改代码如下:

    
    class MyType(type):
      pass
    
    class MyOtherType(MyType):
      pass
    
    class Bar(object, metaclass=MyType):
      pass
    
    
    class Foo(Bar, metaclass=MyOtherType):
      pass
    
    
    print(Bar, Bar.__class__) # <class '__main__.Bar'> <class '__main__.MyType'>
    print(Foo, Foo.__class__) # <class '__main__.Foo'> <class '__main__.MyOtherType'>
    

    可以看到 Bar 和 Foo 分别有自己的元类,并且都符合继承关系中寻找。再调换一下元类看看:

    
    class MyType(type):
      pass
    
    class MyOtherType(MyType):
      pass
    
    class Bar(object, metaclass=MyOtherType):
      pass
    
    
    class Foo(Bar, metaclass=MyType):
      pass
    
    
    print(Bar, Bar.__class__) # <class '__main__.Bar'> <class '__main__.MyOtherType'>
    print(Foo, Foo.__class__) # <class '__main__.Foo'> <class '__main__.MyOtherType'>
    

    都使用了Foo还是使用了元子类作为元类。究其原因,其实也很好理解。定义父类的时候,使用了元类MyOtherType 。定义子类的时候,通过继承,找到了创建父类的元类,那么父类就是 MyOtherType 的实例。

    如果使用 MyType 做为元类,那么他就是 MyType 的实例,MyType的实例会比MyOtherType具有的属性少,那么在继承链上,它又是 Bar的子类,这样看就是子类比父类还狭窄了,显然不是一个好的关系。即变成了下面的关系

    Bar     <-  MyOtherType
     
      |           ↑
      |           |
      ↓           |

    Foo     <-  MyType

    因此当 MyType 是 MyOtherType的父类的时候,即使 Foo 指定了 MyType作为元类,还是会被忽略,使用其父元类MyOtherType。

    上面的线的箭头要一直,才能使用各自指定的元类,否则使用箭头指向的那个类作为元类。元类没有继承关系,元类冲突。

    对象(类)实例化

    目前为止,我们了解了类的定义,即类是如何被元类创建出来的,但是创建的细节尚未涉及。即元类是如何通过实例化创建类的过程。这也是对象创建的过程。

    前文介绍了一个对象是通过类创建的,类对象是通过元类创建的。创建类中,会先调用元类的__new__方法,设置其名称,继承关系和属性,返回一个实例。然后再调用实例的__init__方法进行初始化实例对象。

    
    class MyType(type):
    
      def __init__(self, *args, **kwargs):
        print('init ', id(self), args, kwargs)
    
      def __new__(cls, *args, **kwargs):
        print('new', id(cls), args, kwargs)
        instance = super(MyType, cls).__new__(cls, *args, **kwargs)
        print(id(instance))
        return instance
    
    
    class Bar(object, metaclass=MyType):
      pass
    

    运行代码可以看见输出:

    new 4323381304 (‘Bar’, (<class ‘object’>,), {‘__module__’: ‘__main__’, ‘__qualname__’: ‘Bar’}) {}
    4323382232
    init  4323382232 (‘Bar’, (<class ‘object’>,), {‘__module__’: ‘__main__’, ‘__qualname__’: ‘Bar’}) {}

    注意,上面代码仅关注 Bar 类的创建,即 Bar =MyType('Bar', (object, ), {})这个定义代码。MyType进行实例化创建 Bar的过程中,会先用 其 __new__ 方法,后者调用了父类 type的 __new__方法,并返回了元类的实例, 同时调用这个实例的__init__方法,后者对改实例对象进行初始化。这也就是为什么方法名为 __init__

    通常我们会在 __init__方法初始化一些实例对象的属性如果 __new__ 方法什么也不返回,那么 __init__ 方法是不会被调用的。

    instance = super(MyType, cls).__new__(cls, *args, **kwargs), 有的地方也喜欢写成 type.__new__或者 type,前者是python中如何调用父类方法的问题,后者是直接使用type创建类的过程。比较推荐的写法还是使用 super 调用其父类的方法的方式。

    类是元类的对象,普通类创建对象的过程,也是一样。因此,只要重写 __new__方法,还可以实现一个类还可以创建另外一个类的实例的魔法。

    移花接木

    重写 __new__ 方法,让其创建另外一个类的实例。

    
    class Bar:
      def __init__(self, name):
        self.name = name
        print('Bar init')
    
      def say(self):
        print('say: Bar {}'.format(self.name))
    
    
    class Foo(object):
    
      def __init__(self):
        print('self {}'.format(self))
    
      def __new__(cls, *args, **kwargs):
        instance = super(Foo, cls).__new__(Bar, *args, **kwargs)
        print('instance {}'.format(instance))
        instance.__init__('a class')
        return instance
    
      def say(self):
        print('say: Foo')
    
    
    m = Foo()
    print('m {}'.format(m))
    m.say()
    

    输出

    instance <__main__.Bar object at 0x104033240>
    Bar init
    m <__main__.Bar object at 0x104033240>
    say: Bar a class

    在类 Foo 中,通过重写 __new__返回了一个 Bar 类的实例对象,然后调用 Bar 实例的 __inti__ 方法初始化,由于返回了 bar 实例,因此 Foo 的实例没有被创建,因此也不会调用它的实例方法 __inti__ 。这样就把 移花(Bar)接木(Foo)上了。

    也许有人会觉得这样的诡异魔法有什么用呢?实际上,Tornado框架使用了这样的技术实现了一个叫 Configurable 的工厂类,用于创建不同网络IO下的epoll还是select模型。有兴趣可以参考其实现方式。

    元类的应用

    讨论了那么多原理的东西,最后肯定是要应用到实际中才有意义。既然类可以被动态的创建,那么很多定义在类的方法,岂不是也可以被动态的创建了呢。这样就省去了很多重复工作,也能实现酷酷的元编程。

    元类可以创建单例模式,也可以用来实现 ORM,下面介绍的是Django使用元类实现的查找方式。更经典的model定义网上有很多例子,就不再介绍了。下面介绍一个model通过manger管理器实现查询方法的例子

    
    import inspect
    
    
    class QuerySet:
    
      def get(self, *args, **kwargs):
        print('get method')
        return self
    
      def filter(self, *args, **kwargs):
        print('filter method')
        return self
    
    
    class BaseManager:
    
      def __init__(self):
        pass
    
      @classmethod
      def from_queryset(cls, queryset_class, class_name=None):
        if class_name is None:
          class_name = '%sFrom%s' % (cls.__name__, queryset_class.__name__)
        class_dict = {
          '_queryset_class': queryset_class,
        }
        class_dict.update(cls._get_queryset_methods(queryset_class))
        return type(class_name, (cls,), class_dict)
    
      def get_queryset(self):
        return self._queryset_class()
    
      @classmethod
      def _get_queryset_methods(cls, queryset_class):
        def create_method(name, method):
          def manager_method(self, *args, **kwargs):
            return getattr(self.get_queryset(), name)(*args, **kwargs)
    
          manager_method.__name__ = method.__name__
          manager_method.__doc__ = method.__doc__
          return manager_method
    
        new_methods = {}
        for name, method in inspect.getmembers(queryset_class, predicate=inspect.isfunction):
          if hasattr(cls, name):
            continue
          queryset_only = getattr(method, 'queryset_only', None)
          if queryset_only or (queryset_only is None and name.startswith('_')):
            continue
          new_methods[name] = create_method(name, method)
        return new_methods
    
    
    class Manager(BaseManager.from_queryset(QuerySet)):
      pass
    
    
    class ModelMetaClass(type):
    
      def __new__(cls, *args, **kwargs):
        name, bases, attrs = args
        attrs['objects'] = Manager()
        return super(ModelMetaClass, cls).__new__(cls, name, bases, attrs)
    
    
    class Model(object, metaclass=ModelMetaClass):
      pass
    
    
    class User(Model):
      pass
    
    
    User.objects.get()
    User.objects.filter()
    User.objects.filter().get()
    

    这样model就用使用期管理器Manger 下的方法了。通过model的元类ModelMetaClass,定义model的时候,就初始化了一个 Manger对象挂载到Model下面,而定义Manger的时候,也通过元类将QuerySet下的查询方法挂载到Manger下了。

    总结

    Python里一切都是对象,对象都是由类进行创建实例化而来。既然一切是对象,那么类也是对象,而类这种对象又是由一种更高级类创建而来,即所谓的元类。

    元类可以创建类,Python默认的元类是 type。通过继承type,可以自定义元类,在自定义元类的时候定义或者重载 __new__,可以创建该类的实例对象,同时也可以修改类创建对象的行为。类通过 __new__创建实例对象,然后调用实例对象的 __init__初始化实例对象。

    在使用自定义元类的时候,子类的的元类和父类的元类有关系,前者指定的元类必须和父类的元类是一个继承关系上的,否则会出现元类冲突。子类选取元类的取决于指定的元类和父元类的继承关系,子元类若是父元类的子类,则指定的元类为子元类,否则将会被忽略,使用父元类为其元类。

    元类是元编程的一种技术手段,常用于实现工厂模式的策略。通过定义元类动态创建类和展开,可以实现很多设计精妙的应用。ORM 正式其中一种常用的方法。

    以上就是详解python metaclass(元类)的详细内容,更多关于python metaclass(元类)的资料请关注lingkb其它相关文章!