天天看点

Python metaclass 简介

metaclass

译为

元类

,可以理解为 metaclass 为描述类的超类,同时可以改变子类的形态,本文介绍相关内容。

Python中的类

  • class,对于大部分面向对象语言来说,class是一段定义了如何产生object的代码块。在Python中这一定义也成立。
  • 但是在Python中,class并不只有这一角色。class实际上也是object。当我们使用class定义一个类的时候,Python会执行相应代码并在内存中创建一个对应类的的object。
  • 而metaclass 是控制类实例(这里表示类本身的定义,不是类的实例)产生的”超类“
直接看一个 metaclass的例子可以有更直观的理解

metaclass 实例

class Mymeta(type):
    def __init__(self, name, bases, dic):
        super().__init__(name, bases, dic)
        print('Mymeta.__init__')
        print(self.__name__)
        print(dic)
        print(self.yaml_tag)

    def __new__(cls, *args, **kwargs):
        print('Mymeta.__new__')
        print(cls.__name__)
        return type.__new__(cls, *args, **kwargs)

    def __call__(cls, *args, **kwargs):
        print('Mymeta.__call__')
        obj = cls.__new__(cls)
        cls.__init__(cls, *args, **kwargs)
        return obj


class Foo(metaclass=Mymeta):
    yaml_tag = '!Foo'

    def __init__(self, name):
        print('Foo.__init__')
        self.name = name

    def __new__(cls, *args, **kwargs):
        print('Foo.__new__')
        return object.__new__(cls)

if __name__ == '__main__':
    foo = Foo('foo')
           

复制

  • 输出
Mymeta.__new__
Mymeta
Mymeta.__init__
Foo
{'__module__': '__main__', '__qualname__': 'Foo', 'yaml_tag': '!Foo', '__init__': <function Foo.__init__ at 0x000002626354E550>, '__new__': <function Foo.__new__ at 0x000002626354E5E0>}
!Foo
Mymeta.__call__
Foo.__new__
Foo.__init__           

复制

  • 从上面的运行结果可以发现在定义 class Foo() 定义时,会依次调用 MyMeta 的

    __new__

    __init__

    方法构建 Foo 类,然后在调用 foo = Foo() 创建类的实例对象时,才会调用 MyMeta 的

    __call__

    方法来调用 Foo 类的

    __new__

    __init__

    方法。
在没有 metaclass 的情况下,子类继承父类,父类是无法对子类执行操作的,但有了 metaclass,就可以对子类进行操作,就像装饰器那样可以动态定制和修改被装饰的类,metaclass 可以动态的定制或修改继承它的子类。

meatclass 工作原理

类是 type 类的实例

class TestClass:
    pass

if __name__ == '__main__':
    print(type(TestClass))
    print(isinstance(TestClass, type))           

复制

  • 输出
<class 'type'>
True           

复制

  • 可以看到类是 type 类的实例

用户定义类,是 type 类的

__call__

运算符重载

当我们定义一个类的语句结束时,真正发生的情况,是 Python 调用 type 的

__call__

运算符。

当你定义一个类时,写成下面这样时:

class MyClass:
    data = 1           

复制

Python 真正执行的是下面这段代码:

class = type(classname, superclasses, attributedict)           

复制

这里等号右边的 type(classname, superclasses, attributedict),就是 type 的

__call__

运算符重载,它会进一步调用:

type.__new__(typeclass, classname, superclasses, attributedict)
type.__init__(class, classname, superclasses, attributedict)           

复制

metaclass 是 type 的子类,通过替换 type 的

__call__

运算符重载机制,“超越变形”正常的类

正是 Python 的类创建机制,给了 metaclass 大展身手的机会。一旦你把一个类型 MyClass 的 metaclass 设置成 MyMeta,MyClass 就不再由原生的 type 创建,而是会调用 MyMeta 的

__call__

运算符重载。

class = type(classname, superclasses, attributedict) 
# 变为了
class = MyMeta(classname, superclasses, attributedict)           

复制

metaclass 使用方法

定义属性

当定义class的时候,我们可以使用

__metaclass__

attribute来指定用来初始化当前class的metaclass。如下面的例子所示:

class Foo(object):
    __metaclass__ = something
    [other statements...]           

复制

  • 如果我们指定了

    __metaclass__

    ,Python就是使用这个metaclass来生成class

    Foo

  • 当Python试图创建class

    Foo

    的时候,Python会首先在class的定义中寻找

    __metaclass__

    attribute。如果存在

    __metaclass__

    ,Python将会使用指定的

    __metaclass__

    来创建class

    Foo

    。如果没有指定的话,Python就会使用默认的

    type

    作为metaclass创建

    Foo

显式指定 metaclass

在创建类时显式指定 metaclass 类的内容

class Foo(metaclass=Mymeta):
	pass           

复制

  • 该方法指定的metaclass会被子类继承
  • 也就是说指定 metaclass 之后,Foo 类及其子类在默认情况下都会使用 Mymeta 作为 metaclass
  • 这就是控制子类行为的方法

继承父类 metaclass

对于下面这个例子:

class Foo(Bar):
    pass           

复制

  • Python首先在

    Foo

    中寻找是否存在

    __metaclass__

    attribute。
  • 如果存在的话,Python将使用这个metaclass在内存中创建一个名字为

    Foo

    的class object。如果Python
  • 如果class定义中不存在

    __metaclass__

    的话,Python将会寻找MODULE级别的

    __metaclass__

    。如果存在的话就进行与前述相同的操作。
  • 当Python仍然没有找到

    __metaclass__

    时,Python将会使用当前类的父类的metaclass来创建当前类。在我们上面这个例子中,Python会使用

    Foo

    的父类

    Bar

    的metaclass来创建

    Foo

    的class object。
同时需要注意的是,在class中定义的

__metaclass__

attribute并不会被子类继承。被子类继承的是母类的metaclass,也就是母类的

.__class__

attribute。比如上面的例子中,

Bar.__class__

将会被

Foo

继承。也就是说,如果

Bar

定义了一个

__metaclass__

attribute来使用

type()

创建

Bar

的class object(而非使用

type.__new__()

),那么

Bar

的子类,也就是

Foo

,并不会继承这一行为。

使用 metaclass 的风险

不过,凡事有利必有弊,尤其是 metaclass 这样“逆天”的存在。正如你所看到的那样,metaclass 会"扭曲变形"正常的 Python 类型模型。所以,如果使用不慎,对于整个代码库造成的风险是不可估量的。

换句话说,metaclass 仅仅是给小部分 Python 开发者,在开发框架层面的 Python 库时使用的。而在应用层,metaclass 往往不是很好的选择。

参考资料

  • https://zhuanlan.zhihu.com/p/98440398
  • https://www.jianshu.com/p/224ffcb8e73e