Bootstrap

《Python源码剖析》之PyTypeObject

前言

这一篇博客原本应该是写在上一篇关于pyObject对象的博客中的,但是为了不把内容写的又臭又长,给读者减轻痛苦,给我也减少压力,于是就专门用一篇介绍一下今天的主角—pyTypeObject。

开始

还记得在上一篇我们有聊到,pyObject的结构体很简单,总共只有三项,其中一项就是类型为pyTypeObject的变量ob_type
image.5baf5f54fee311eebf312b20f7a91591.png
这个被遗留的小东西,看似简单,但实际上却承载了非凡的使命!我们都知道python是面向对象的编程语言,之前我们一直在探究对象层面的东西,但是我们要知道,得到一个对象的前提是需要有类[型]的,不然我怎么知道这是一个什么对象?在不知道它是一个什么对象的前提下,就更不会知道它会有什么行为或者属性…而pyTypeObject的的存在就是为了解决这些问题,我认为它在某种意义上来说,就是面向对象中的含义的具体实现!

来看看它的具体定义吧!内容看起来有点多,我这里就只截取部分出来:

image.5092e382fee511eebf312b20f7a91591.png

// If this structure is modified, Doc/includes/typestruct.h should be updated
// as well.
struct _typeobject {
    PyObject_VAR_HEAD
    const char *tp_name; /* For printing, in format "<module>.<name>" */
    Py_ssize_t tp_basicsize, tp_itemsize; /* For allocation */

    /* Methods to implement standard operations */

    destructor tp_dealloc;
    Py_ssize_t tp_vectorcall_offset;
    getattrfunc tp_getattr;
    setattrfunc tp_setattr;
    PyAsyncMethods *tp_as_async; /* formerly known as tp_compare (Python 2)
                                    or tp_reserved (Python 3) */
    reprfunc tp_repr;

    /* Method suites for standard classes */

    PyNumberMethods *tp_as_number;
    PySequenceMethods *tp_as_sequence;
    PyMappingMethods *tp_as_mapping;

    /* More standard operations (here for binary compatibility) */

    hashfunc tp_hash;
    ternaryfunc tp_call;
    reprfunc tp_str;
    getattrofunc tp_getattro;
    setattrofunc tp_setattro;

    /* Functions to access object as input/output buffer */
    PyBufferProcs *tp_as_buffer;

    /* Flags to define presence of optional/expanded features */
    unsigned long tp_flags;

    const char *tp_doc; /* Documentation string */

    /* Assigned meaning in release 2.0 */
    /* call function for all accessible objects */
    traverseproc tp_traverse;

    /* delete references to contained objects */
    inquiry tp_clear;

    /* Assigned meaning in release 2.1 */
    /* rich comparisons */
    richcmpfunc tp_richcompare;

    /* weak reference enabler */
    Py_ssize_t tp_weaklistoffset;

    /* Iterators */
    getiterfunc tp_iter;
    iternextfunc tp_iternext;

    /* Attribute descriptor and subclassing stuff */
    PyMethodDef *tp_methods;
    PyMemberDef *tp_members;
    PyGetSetDef *tp_getset;
    // Strong reference on a heap type, borrowed reference on a static type
    PyTypeObject *tp_base;
    PyObject *tp_dict;
    descrgetfunc tp_descr_get;
    descrsetfunc tp_descr_set;
    Py_ssize_t tp_dictoffset;
    initproc tp_init;
    allocfunc tp_alloc;
    newfunc tp_new;
    freefunc tp_free; /* Low-level free-memory routine */
    inquiry tp_is_gc; /* For PyObject_IS_GC */
    PyObject *tp_bases;
    PyObject *tp_mro; /* method resolution order */
    PyObject *tp_cache;
    PyObject *tp_subclasses;
    PyObject *tp_weaklist;
    destructor tp_del;

    /* Type attribute cache version tag. Added in version 2.6 */
    unsigned int tp_version_tag;

    destructor tp_finalize;
    vectorcallfunc tp_vectorcall;
};

根据它的变量名,结合它的注释是不是看起有点亲切的样子,比如说第一个变量tp_name,它给出的解释是为了格式化打印,我们来动手打印几个类型试试:image.97d68f98fee811eebf312b20f7a91591.png
发现确实前面是模块,后面是具体的名这样的格式!不知道这样会不会让你对这个源码会有一点亲切感,仿佛找到了python它的老家哈哈🤓🤓
当然还有其他很多变量,比如:tp_getattrtp_setattr,其实我们平常调用的getattr和setattr就是他俩;tp_hash,我们调用hash函数时实际上执行的就是这个;tp_mro方法的调用顺序等等,可以在这里好好的和python叙叙旧哈哈

python中的三大类型

又到了分类的时间了哈哈哈,之前我们按照对象的长度是否可变可以分为变长对象和不可变长对象,这次,我们分类的准则是:对象的行为。这个听起来也许有点抽象,简单来说就是:我们可以把具有相似行为和属性的一些对象归为一类。又让我联想到了鸭子🦆类型的定义哈哈哈

我们来看一个具体的例子:
python中的int和float对象,int对象可以进行加减乘除,乘方,开方等等一系列的数学运算行为;而float对象也可以进行加减乘除等等一系列的数学运算,那么我们就可以把它们归为一类,因为它们的行为高度相似!
:::info
面向对象中类的一个概念:类是对象行为的高度抽象。这个听起来比较抽象,感觉有一种编程哲学的感觉🤣,但是其实就是上面的意思。(个人观点,不知道你赞不赞同)

:::
在python中,根据对象的行为,定义了最基本的三大类型,它们分别是:tp_as_numbertp_as_sequencetp_as_mapping,分别对应的就是number(数字),sequence(序列)和mapping(映射)。而这个定义就被包含在了pyTypeObject的结构体中:
image.26a8a356feed11eebf312b20f7a91591.png
因此,我们可以说,pyTypeObject肩负的责任真的不容小觑!接下来,我们再来看看它是如何具体实现的。
image.aed9b8cafef011eebf312b20f7a91591.png

具体到每一个类型的结构体内部,可以发现,其实就是定义(抽象)了每一个类型下最基本的方法,那么何时何地去实现的这些方法呢?还记得我们刚开始说python的源码组织那篇博客吗?通过它,我们可以知道,python中所有内置对象的定义都包含在了Objects这个目录下,我们可以在这个目录中寻找到float对象类型的定义如下:
image.46638d46fef211eebf312b20f7a91591.png
首先,它定义了一个PyNumberMethods类型的float_as_number,也就是我们上面说的number类型,同时它在内部实现了add,sub,mul等一些基本的运算方法,然后在定义float类型PyFloat_Type时直接引用float_as_number地址,这样就get了作为number类型的一些基本行为,有了这些行为,我们就可以说它是一个number类啦!或者,在这里我们可以更加具体的一点说,它就是具有number行为的float类型啦!

同理,如果你感兴趣的话,你也可以继续去探究int,list,tuple,dict,str等其他的python内置对象,你肯定会发现,这些内置对象也一定属于这三大类型中的其中一种!加油,去开启你的探索吧!

总结

通过对pyTypeObject的探究,我们可以知道,每一个对象都会有一个自己的pyTypeObject,因为它们都会有自己的类;在pyTypeObject结构体内部它定义了三大类型numbersequencemapping各自的结构体,可以理解成最基本的行为集合,通过实现其中任何一种类型中全部或部分的行为定义,就可以实现一个具体的类,以float类型为例,我们只需要实现number结构体中的加减乘除等一系列的行为,那么我就可以认为它是一个number类型,同时它也是python中一个具体的类型了,以此类推,python中的int,str,list,dict等类型的实现也是如此,有了这些基本的类型,就足以构建起python中面向对象最基本的类型生态了!

更多内容可以关注博主的个人博客系统:《Python源码剖析》之PyTypeObject

;