vlambda博客
学习文章列表

《源码探秘 CPython》69. 给类型对象设置类型和基类信息

在上一篇文章中我们说道,内置类对象虽然在底层静态定义好了,但是还不够完善。解释器在启动之后还要再打磨一下,然后才能得到我们平时使用的类型对象,而这个过程被称为类型对象的初始化。


类型对象的初始化,是通过 PyType_Ready 函数实现的,我们来看一下,它位于 Objects/typeobject.c 中。另外由于初始化这部分内容比较多,接下来我们准备用三篇文章去介绍它。

intPyType_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 是一个极其繁复的过程,我们下一篇文章再说。