metaclass、type以及python中关于class的那些事

type

在python中,type()可以查看一个对象的类型,也可以创建一个新的类型。
我们都知道,python作为动态语言相比于静态语言的一个显著区别就是类和函数的定义是运行时动态创建的(而非编译时定义好)。
我们可以这样定义一个类

1
2
class A(object):
pass

也可以这样定义

1
A = type('A', (object,), {})

上述两种方式构造的A类是等价的。

class的type

先看一段代码

1
a = A()

上述代码里我们创建了A类的实例(instance),命名为a。很显然a是一个对象,并且由于在python中万物皆对象,实际上A也是一个对象。如果我们尝试对a和A用type查看他们的类型,会发现一些有趣的事情。

1
2
type(a)  # <class '__main__.A'>
type(A) # <type 'type'>

事实上如果你尝试对别的类用type,你会发现它们的输出都是<type 'type'>

metaclass

承接上文中我们说的,类(class)本质上也是一个对象。在python里,内存里的一个对象一定是被创建出来的。
在上文的代码中我们发现A类可以被type直接创建出来,除此之外,还可以使用元类(metaclass)。
这里引用一段我认为非常经典的关于元类与类关系的描述

1
2
当我们定义了类以后,就可以根据这个类创建出实例,所以:先定义类,然后创建实例。
但是如果我们想创建出类呢?那就必须根据metaclass创建出类,所以:先定义metaclass,然后创建类。

所以,metaclass允许你创建类或者修改类。换句话说,你可以把类看成是metaclass创建出来的“实例”。
在下面的示例中,MyClass和MySubclass都是MetaClass的实例:

1
2
3
4
5
6
7
8
9
class MetaClass(type):
pass

class MyClass(object):
__metaclass__ = MetaClass
pass

class MySubclass(MyClass):
pass

这里有一点绕的地方在于,所有metaclass的父类都是type或者源自type,type可以认为在python中是奇点般的存在,由它生出万物。

__init____new____call__

稍微熟悉python的人都知道,__init____new____call__这三个方法在类的实例创建中有重要作用。

1
2
a = MyClass()
a()

当我们通过上述代码的方式来构造MyClass类的一个实例时,实际上会先调用MyClass__new__方法(如果没有就会去父类上面找),然后再调用__init__方法。
另外我们知道对象通过提供__call__(slef, *args ,**kwargs)方法可以模拟函数的行为,如果一个对象提供了该方法,就可以像函数一样使用它。也就是说a(arg1, arg2...) 等同于调用a.__call__(self, arg1, arg2) ,而a()实际上就是调用MyClass__call__方法,即调用了对象a的类型上的__call__方法。

在弄清上面的原理后,我们再看向a = MyClass()这句代码。通过上面的介绍,我们知道MyClass也是一个对象,而它的类型就是它的metaclass,即我们上面定义的MetaClass类。所以,MyClass()实际上会调用MyClass的类型,也即是MetaClass__call__方法。而这,也正是很多代码中通过重写自身metaclass中的__call__方法从而达到控制自身以及子类的初始化逻辑。

metaclass、type以及python中关于class的那些事

http://xulanting.net/2020/12/13/post10/

Author

John Doe

Posted on

2020-12-13

Updated on

2021-11-17

Licensed under

You need to set install_url to use ShareThis. Please set it in _config.yml.

Comments

You forgot to set the shortname for Disqus. Please set it in _config.yml.