当前位置: 移动技术网 > IT编程>脚本编程>Python > 详解python metaclass(元类)

详解python metaclass(元类)

2020年08月17日  | 移动技术网IT编程  | 我要评论
元编程,一个听起来特别酷的词,强大的lisp在这方面是好手,对于python,尽管没有完善的元编程范式,一些天才的开发者还是创作了很多元编程的魔法。django的orm就是元编程的一个很好的例子。本篇

元编程,一个听起来特别酷的词,强大的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(元类)的资料请关注移动技术网其它相关文章!

如您对本文有疑问或者有任何想说的,请点击进行留言回复,万千网友为您解惑!

相关文章:

验证码:
移动技术网