使用C语言编写Python扩展3——创建自定义类型(1)

在Python代码中如果要创建一个自定义类使用class关键字即可,但是在C代码中就没那么方便了。

首先简单介绍下Python中的类型。在python中一切皆对象,python中有两种对象:

一种是类型对象(class对象):表示Python定义的类型,例如int, str, object等;

另一种是实例对象(instance对象):表示由class对象创建的实例。
Python中的所有对象都是直接或者间接继承object,然后object又是typy类型。可以运行下面的例子看看输出结果:

class A(object):
    pass

a = A()

print(type(a))
print(isinstance(a, A))
print(isinstance(a, object))
print(isinstance(a, type))

print(type(A))
print(A.__base__)
print(isinstance(A, object))
print(isinstance(A, type))

print(type(object))
print(isinstance(object, type))

print(type(type))
print(isinstance(type, object))

python是一门面向对象的编程语言,它是用C写的,而C又是面向过程的编程语言,那么python的类在C中是如何实现的呢?答案就是用结构体来模拟。

在Python的object.h头文件中定义了一个重要的结构体 PyTypeObject 。创建新的类型就是靠的它,该结构体定义如下:

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

    /* Methods to implement standard operations */

    destructor tp_dealloc;
    printfunc tp_print;
    getattrfunc tp_getattr;
    setattrfunc tp_setattr;
    cmpfunc tp_compare;
    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 */
    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;

    /* 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;
} PyTypeObject;

这个比较庞大,里面包含的数据比较多,大部分都是一些函数指针而且可以为空,至于每个字段是什么意思请查看Python文档。

创建自定义类型

创建一个新的C代码文件 noddy.c ,然后我们编写一个名为 noddy 的扩展模块,该模块包含了一个名为 Noddy 的类。

首先创建一个新的 PyTypeObject 类型的变量:

typedef struct {
    PyObject_HEAD
    /* Type-specific fields go here. */
} noddy_NoddyObject;
static PyTypeObject noddy_NoddyType = {
    PyVarObject_HEAD_INIT(NULL, 0)
    "noddy.Noddy",             /*tp_name*/
    sizeof(noddy_NoddyObject), /*tp_basicsize*/
    0,                         /*tp_itemsize*/
    0,                         /*tp_dealloc*/
    0,                         /*tp_print*/
    0,                         /*tp_getattr*/
    0,                         /*tp_setattr*/
    0,                         /*tp_compare*/
    0,                         /*tp_repr*/
    0,                         /*tp_as_number*/
    0,                         /*tp_as_sequence*/
    0,                         /*tp_as_mapping*/
    0,                         /*tp_hash */
    0,                         /*tp_call*/
    0,                         /*tp_str*/
    0,                         /*tp_getattro*/
    0,                         /*tp_setattro*/
    0,                         /*tp_as_buffer*/
    Py_TPFLAGS_DEFAULT,        /*tp_flags*/
    "Noddy objects",           /*tp_doc*/
};

这里是定义了一个noddy_NoddyObject结构体,它的第一个字段为 PyObject_HEAD ,因此相当于一个PyObject类型;然后还有一个 noddy_NoddyType 变量,它的第一个字段为 PyVarObject_HEAD_INIT(NULL, 0) ,这个很很重要,按理说这个应该写成 PyVarObject_HEAD_INIT(&PyType_Type, 0) ,即表示Noddy这个类是一个type类型的对象。不过有的C编译器会对这个报错,因此这一项将在后面调用PyType_Ready函数来填充。
noddy_NoddyType 即是 Noddy 类,它保存了该类的元信息;noddy_NoddyObject结构体用于保存该类的实例对象的数据。
只要是定义的结构体以PyObject_HEAD开始就属于是一个PyObject类型。PyObject_VAR_HEAD与PyObject_HEAD相似,只是PyObject_HEAD表示的是该类型占用内存大小是固定的如int、float;而PyObject_VAR_HEAD表示该类型占用的内存是可变的如list、dict。

然后创建一个新扩展模块,并完成初始化:

static PyMethodDef noddy_methods[] = {
    {NULL}  /* Sentinel */
};
PyMODINIT_FUNC
initnoddy(void) 
{
    PyObject* m;

    noddy_NoddyType.tp_new = PyType_GenericNew;
    if (PyType_Ready(&noddy_NoddyType) < 0)
        return;

    m = Py_InitModule3("noddy", noddy_methods,
                       "Example module that creates an extension type.");

    Py_INCREF(&noddy_NoddyType);
    PyModule_AddObject(m, "Noddy", (PyObject *)&noddy_NoddyType);
}

注意:以上是针对Python2的,在Python3中模块的初始化操作略有不同。请参考第一节的内容。

noddy_NoddyType即是我们要创建的 Noddy 类,它是 PyTypeObject 类型的结构变量。为了创建新的类型,我们需要指明 tp_new 方法,它相当于Python中的 __new__,这里我们使用默认的 PyType_GenericNew 即可。
然后调用 PyType_Ready 完成新类型的创建。
最后调用 PyModule_AddObject 在该模块中添加刚刚创建的新类型。

测试

最后就是编写一个小程序来测试刚刚的模块是否可用。

import noddy

o = noddy.Noddy()
print(o)
print(type(o), type(noddy.Noddy))

# 这个会报错,noddy.Noddy 类不能被继承
class A(noddy.Noddy):
    pass