内置类对象虽然在底层静态定义好了,但是还不够完善。解释器在启动之后还要再打磨一下,然后才能得到我们平时使用的类型对象,而这个过程被称为类型对象的初始化。
类型对象的初始化,是通过 PyType_Ready 函数实现的,我们来看一下,它位于 Objects/typeobject.c 中。另外由于初始化这部分内容比较多,接下来我们准备用三篇文章去介绍它。
int PyType_Ready(PyTypeObject *type) { //这里的参数显然是类型对象 //以 <class 'type'> 为例 //dict:属性字典 //bases:继承的所有基类,即 __bases__ PyObject *dict, *bases; //base:继承的第一个基类,即 __base__ PyTypeObject *base; Py_ssize_t i, n; //...... //...... //获取类型对象中 tp_base 成员指定的基类 base = type->tp_base; if (base == NULL && type != &PyBaseObject_Type) { //如果基类为空、并且该类本身不是object //那么将该类的基类设置为 object、即 &PyBaseObject_Type //所以一些类型对象在底层定义的时候,tp_base 成员为空 //因为tp_base是在这里、也就是初始化的时候进行设置的 base = type->tp_base = &PyBaseObject_Type; Py_INCREF(base); } //如果基类不是NULL,也就是指定了基类 //但是基类的属性字典是NULL if (base != NULL && base->tp_dict == NULL) { //说明该类的基类尚未初始化,那么会先对基类进行初始化 //注意这里的 tp_dict,它表示每个类都会有的属性字典 //而属性字典是否为 NULL,是类型对象是否初始化完成的重要标志 if (PyType_Ready(base) < 0) goto error; } //如果该类型对象的 ob_type 为空,但是基类不为空 //那么将该类型对象的 ob_type 设置为基类的 ob_type //为什么要做这一步, 我们后面会详细说 if (Py_TYPE(type) == NULL && base != NULL) Py_TYPE(type) = Py_TYPE(base); //获取 __bases__,检测是否为空 bases = type->tp_bases; //如果为空,则根据 __base__ 进行设置 if (bases == NULL) { //如果 base 也为空,那么 bases 就是空元祖 //而base如果为空了,说明当前的类对象一定是object if (base == NULL) bases = PyTuple_New(0); //如果 base 不为空,那么 bases 就是 (base,) else bases = PyTuple_Pack(1, base); if (bases == NULL) goto error; //设置 tp_bases type->tp_bases = bases; } //设置属性字典,后续再聊 dict = type->tp_dict; if (dict == NULL) { dict = PyDict_New(); if (dict == NULL) goto error; type->tp_dict = dict; } //...... }
对于指定了tb_base的类对象,当然就使用指定的基类,而对于没有指定tp_base的类对象,虚拟机将为其指定一个默认的基类:&PyBaseObject_Type ,也就是 Python 的 object。
现在我们看到 PyType_Type 的 tp_base 指向了 PyBaseObject_Type,这在Python中体现的就是 type 继承自 object、或者说 object 是 type 的父类。但是所有的类的 ob_type 又都指向了 PyType_Type,包括 object,因此我们又说 type 是包括 object 在内的所有类的类(元类)。
而在获得了基类之后,会判断基类是否被初始化,如果没有,则需要先对基类进行初始化。可以看到,判断初始化是否完成的条件是tp_dict是否为NULL,这符合之前的描述。对于内置类对象来说,在解释器启动的时候,就已经作为全局对象存在了,所以它们的初始化不需要做太多工作,只需小小的完善一下即可,比如设置基类、类型、以及对 tp_dict 进行填充。
在基类设置完毕后,会继续设置 ob_type,这个ob_type就是 class 返回的类型对象。
首先 PyType_Ready 函数里面接收的是一个 PyTypeObject 对象,我们知道这个在 Python 中就是类对象。因此这里是设置这些类对象的 ob_type,那么对应的显然就是元类(metaclass),我们自然会想象到Python的type。
而Py_TYPE(type) = Py_TYPE(base)这一行代码是把父类的ob_type设置成了当前类的ob_type,那么这一步的意义何在呢?我们使用Python来演示一下。
class MyType(type):
pass
class A(metaclass=MyType):
pass
class B(A):
pass
print(type(A)) # <class '__main__.MyType'>
print(type(B)) # <class '__main__.MyType'>
我们看到B继承了A,而A的类型是MyType,那么B的类型也成了MyType。也就是说 A 是由 XX 生成的,那么B在继承A之后,B 也会由 XX 生成,所以源码中的那一步就是用来做这件事情的。另外,这里之所以用 XX 代替,是因为 Python 里面不仅仅只有 type 是元类,那些继承了 type 的子类也可以是元类。
而且如果你熟悉 flask 的话,你会发现 flask 源码里面就有类似于这样的操作:
class MyType(type): def __new__(mcs, name, bases, attrs): # 关于第一个参数我们需要说一下 # 对于一般的类来说这里应该是cls # 但我们这里是元类,所以应该用mcs,意思就是metaclass # 我们额外设置一些属性吧,关于元类我们后续会介绍 # 虽然目前还没有看底层实现,但至少使用方法应该知道 attrs.update({"name": "古明地觉"}) return super().__new__(mcs, name, bases, attrs) def with_metaclass(meta, bases=(object, )): return meta("", bases, {}) class Girl(with_metaclass(MyType, (int,))): pass print(type(Girl)) # <class '__main__.MyType'> print(getattr(Girl, "name")) # 古明地觉 print(Girl("123")) # 123
所以逻辑很清晰了,虚拟机就是将基类的metaclass设置为子类的metaclass。对于当前的PyType_Type来说,其metaclass就是object的metaclass,也是它自己。而在源码的PyBaseObject_Type中也可以看到,其ob_type被设置成了 &PyType_Type。
if (Py_TYPE(type) == NULL && base != NULL) Py_TYPE(type) = Py_TYPE(base);
tb_base 和 ob_type 设置完毕之后,会设置 tb_bases。tb_base 对应 __base__,tb_bases 对应 __bases__,我们用 Python 演示一下,这两者的区别。
class A:
pass
class B(A):
pass
class C:
pass
class D(B, C):
pass
print(D.__base__) # <class '__main__.B'>
print(D.__bases__) # (<class '__main__.B'>, <class '__main__.C'>)
print(C.__base__) # <class 'object'>
print(C.__bases__) # (<class 'object'>,)
print(B.__base__) # <class '__main__.A'>
print(B.__bases__) # (<class '__main__.A'>,)
我们看到 D 同时继承多个类,那么 tp_base 就是先出现的那个基类。而 tp_bases 则是继承的所有基类,但是基类的基类是不会出现的,比如 object。对于 B 而言也是一样的。
然后我们看看 C,因为 C 没有显式地继承任何类,那么 tp_bases 就是NULL。但是Python3里面所有的类都默认继承了object,所以tp_base就是object。而 tp_bases,显然是 (object,)。
以上就是 tp_base、ob_type、tp_bases 的设置,还是比较简单的,它们在设置完毕之后,就要对 tp_dict 进行填充了。而填充 tp_dict 是一个极其繁复的过程,我们下一篇文章再说。
以上就是本次分享的所有内容,想要了解更多欢迎前往公众号:Python编程学习圈,每日干货分享