Python内部机制-PyTypeObject对象

PyTypeObject对象

不知道你还否还记得在我的上一篇文章中关于PyObject中的ob_type成员,在上篇文章中我没有对它进行展开,只是告诉读者这是用来指明当前对象是何种类型以及一些类型相关的信息.那么本篇博文则是单独把ob_type拿出来分析,通过查看PyObject对象,你可以发现ob_type成员是一个struct _typeobject而这个数据结构在Python内部有另外一个typedef的别名,那就是PyTypeObject这也是本文需要分析的一个内部对象.因为这个数据结构很长,有很多的数据成员,为了让代码更易读,我不会全部列出所有的成员,而是一点点揭开面纱.

PyTypeObject的用途?

Python官方文档上说PyTypeObject或许是Python对象机制中最为重要的一个结构体,因为这个结构体可以用来定义新的类型,这个结构体控制了对象的行为.此外这个结构体的相比于Python内部的其它对象而言也是相当的大,这是因为这个结构体需要存储大量的数据成员和大量的C函数指针, 这其中包含了了一部分与类型相关的函指针.总的来说PyTypeObject就是用来描述一种类型对象的行为的结构体,比如对于一个int类型的对象和string类型的对象,两者的初始化肯定不一样,两者的打印输出方式,两者的比较运算方式肯定都不一样等等,因此对于每一种类型对象来说都要有一个PyTypeObject对象保存与这个类型相关的一些数据成员和函数指针.并且每个类型对象在初始的时候都会初始化PyTypeObject对象,类型信息就固定下来了.具体细节可以参考附录中关于如何自定义新的类型对象.不知道上面说了这么一大通的理论大家是否理解了PyTypeObject的用途? 下面从Python层面我们来看看PyTypeObject的用途.

>>> a = 1
>>> print a
>>> sa = "zhang"
>>> print sa

上面简单的初始化了一个int对象和一个string对象,那么在初始化的时候malloc应该分配多大的空间呢?,这个信息其实存放在PyTypeObject中,print打印这两个对象的是如何知道怎么打印的呢?因为在C语言层面打印一个字符串和打印一个整数是不一样的,那么对于每种类型该如何打印输出也是存放在PyTypeObject中的.就拿int类型的对象来说它的大小放在PyTypeObject对象的tp_basicsize数据成员中,可以通过int对PyTypeObject初始化的代码中看到.

typedef struct _typeobject {
    PyObject_VAR_HEAD
    const char *tp_name; /* For printing, in format "." */                                                                                                  
    Py_ssize_t tp_basicsize, tp_itemsize; /* For allocation */
    ......  //此处省略N多成员
    printfunc tp_print; //用于输出类型对象的值
    ......
}PyTypeObject;

./Include/object.h

int对象的PyTypeObject初始化

PyTypeObject PyInt_Type = {
    PyVarObject_HEAD_INIT(&PyType_Type, 0)
    "int",
    sizeof(PyIntObject),
    ...... //次数省略N多成员的初始化
    (printfunc)int_print,  //打印函数的实现
    ......
};

./Object/intobject.c

可以看到tp_basicsize的大小是sizeof(PyIntObject),类型的名字是int,看到这里是不是有点明白了?,我们再来看看如何打印int型对象的值.在PyTypeObject中有一个printfunc类型的函数指针成员tp_print,每一种类型要想可以被输出就需要去实现这个函数.我们来看看int对象实现的int_print函数

static int
int_print(PyIntObject *v, FILE *fp, int flags)                                                                                                                            
     /* flags -- not used but required by interface */
{
    long int_val = v->ob_ival;
    Py_BEGIN_ALLOW_THREADS
    fprintf(fp, "%ld", int_val);
    Py_END_ALLOW_THREADS
    return 0;
}

./Object/intobject.c
忽略那些你目前还看不懂的两个宏Py_BEGIN_ALLOW_THREADS,Py_END_ALLOW_THREADS,思路很简单,就是获取ob_ival值,然后通过fprintf进行打印.如此的简单.我相信通过上面的两个例子,你应该懂了PyTypeObject对象的重要性和用途了吧.

PyTypeObject的组成

上面对PyTypeObject对象用途的介绍还只是九牛一毛,接下来让我们捋一捋PyTypeObject对象由哪些部分组成,每个部分的作用何在.

typedef struct _typeobject {
    PyObject_VAR_HEAD
    char *tp_name; /* For printing, in format "." */
    int tp_basicsize, tp_itemsize; /* For allocation */
    .....
}; 

第一个部分是一些必须的类型信息,因为PyTypeObject本身也是一个对象,所以PyObject_VAR_HEAD这是必不可少的了,其次就是类型的名称,类型的基本大小,类型中的元素大小,对于int对象来tp_itemsize是0,因为int对象没有元素的概念,本身就是一个整体,而string对象的tp_itemsize就是1,string对象的基本元素就是char.

typedef struct _typeobject {
    ......
 /* Methods to implement standard operations */
    destructor tp_dealloc;
    printfunc tp_print;
    getattrfunc tp_getattr;
    setattrfunc tp_setattr;
    cmpfunc tp_compare;
    reprfunc tp_repr;
    .....
}; 

第二个部分是一些标准的类型相关的操作,比如如何释放类型对象的内存,如何打印类型对象的值,如何在两个类型对象之间进行比较,调用python内置函数repr的时候打印输出什么?

typedef struct _typeobject {
    ......
   /* Method suites for standard classes */

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

第三个部分是标准对象所支持的操作,比如说一个对象如果被认为是数值对象,那么这个对象应该支持哪些操作,每种操作的具体实现是什么样的,如果一个对象被认为是一个序列对象,那么应该支持哪些操作,如果一个对象被认为是关联对象,那么应该支持哪些操作,所以这里又引出了Python中另外一个概念,从都系类型角度来分类的化,Python中有三种对象,一种是数值对象,一种是序列对象,最后一种是关联对象.就那int类型的对象来说,我们来看看作为一个数值对象,应该需要有哪些操作.

static PyNumberMethods int_as_number = {
    (binaryfunc)int_add,        /*nb_add*/
    (binaryfunc)int_sub,        /*nb_subtract*/
    (binaryfunc)int_mul,        /*nb_multiply*/
    (binaryfunc)int_classic_div, /*nb_divide*/
    (binaryfunc)int_mod,        /*nb_remainder*/
    (binaryfunc)int_divmod,     /*nb_divmod*/
    (ternaryfunc)int_pow,       /*nb_power*/
    (unaryfunc)int_neg,         /*nb_negative*/
    (unaryfunc)int_int,         /*nb_positive*/
    (unaryfunc)int_abs,         /*nb_absolute*/
    (inquiry)int_nonzero,       /*nb_nonzero*/
    (unaryfunc)int_invert,      /*nb_invert*/
    (binaryfunc)int_lshift,     /*nb_lshift*/
    (binaryfunc)int_rshift,     /*nb_rshift*/
    (binaryfunc)int_and,        /*nb_and*/
    (binaryfunc)int_xor,        /*nb_xor*/
    (binaryfunc)int_or,         /*nb_or*/
    int_coerce,                 /*nb_coerce*/
    (unaryfunc)int_int,         /*nb_int*/
    (unaryfunc)int_long,        /*nb_long*/
    (unaryfunc)int_float,       /*nb_float*/
    (unaryfunc)int_oct,         /*nb_oct*/
    (unaryfunc)int_hex,         /*nb_hex*/
    0,                          /*nb_inplace_add*/
    0,                          /*nb_inplace_subtract*/
    0,                          /*nb_inplace_multiply*/
    0,                          /*nb_inplace_divide*/
    0,                          /*nb_inplace_remainder*/
    0,                          /*nb_inplace_power*/
    0,                          /*nb_inplace_lshift*/
    0,                          /*nb_inplace_rshift*/
    0,                          /*nb_inplace_and*/
    0,                          /*nb_inplace_xor*/
    0,                          /*nb_inplace_or*/
    (binaryfunc)int_div,        /* nb_floor_divide */
    (binaryfunc)int_true_divide, /* nb_true_divide */
    0,                          /* nb_inplace_floor_divide */
    0,                          /* nb_inplace_true_divide */
    (unaryfunc)int_int,         /* nb_index */
};

上面就是作为一个数值对象来说,应该具备的一些操作.

typedef struct _typeobject {
    .......
    /* Functions to access object as input/output buffer */
    PyBufferProcs *tp_as_buffer;

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

    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 */
    long tp_weaklistoffset;

    /* Added in release 2.2 */
    /* Iterators */
    getiterfunc tp_iter;
    iternextfunc tp_iternext;
    ......
};

第三部分则是和迭代器,弱引用,文档字符串等相关的一些字段,限于篇幅这里不再介绍了.

typedef struct _typeobject {
    ......
    /* Attribute descriptor and subclassing stuff */
    struct PyMethodDef *tp_methods;
    struct PyMemberDef *tp_members;
    struct PyGetSetDef *tp_getset;
    struct _typeobject *tp_base;
    PyObject *tp_dict;
    descrgetfunc tp_descr_get;
    descrsetfunc tp_descr_set;
    long 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;
    .......
};

第四部分是类型对象的属性信息,tp_methods则指定了类型对象具有的成员方法,比如string对象就有join,append等成员方法,tp_members则指明了类型对所具有的数据成员等等.tp_getset指明了类型对象具备的一些get类和set类的操作,tp_base,则指明了类型对象的基类.
关于PyTypeObject更多相关的成员的用途和解释可以参考附录中的文档.

Python对象的多态性

通过PyTypeObject在C语言层面实现了所谓的多态.就拿打印来说, 下面这个函数可以应用与任何类型的对象上,都可以正常的打印类型的值.

void print(PyObject* object)
{
    object->ob_type->tp_print(object);
}

对于上面的函数,如果传入的是string类型对象,那么调用的就是string类型对象的PyTypeObject对象,最终调用的tp_print就是string类型对象自己自己实现的打印函数,同理如果传入的是int类型对象,那么最终调用的tp_print则是int类型对象自己实现的打印函数. 这就是python中多态的实现.

附录

如何自定义新的类型
Type Object

你可能感兴趣的:(Python基础与进阶)