使用C语言为python编写动态模块--在C中实现python中的类

Posted traditional

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了使用C语言为python编写动态模块--在C中实现python中的类相关的知识,希望对你有一定的参考价值。

楔子

这次我们来介绍python中的类型在C中是如何实现的,我们在C中创建python的int对象,可以使用PyLong_FromLong、创建python的list对象可以使用PyList_New,那么如何在C中构建一个python中的类呢?

对于构建一个类,我们肯定需要以下步骤:

  • 创建一个类扩展
  • 添加类的参数
  • 添加类的方法
  • 添加类的属性,比如可以设置、获取属性
  • 添加类的继承
  • 解决类的循环引用导致的内存泄露问题和自定义垃圾回收

前面几个步骤是必须的,但是容易把最后一个问题给忽略掉。我们在python中编写模块的话,那么内存方面的问题不需要我们来考虑,但是当编写扩展模块的时候,我们是在C中编写的,因此内存方面都需要我们自己来管理。
python是通过引用计数来决定一个对象是否应该被回收,这是最简单、最原始、但也是最方便的方法。尽管它有很多的不足,python还是采用了这种方法,因为它非常简单、直观。但是也正如我们说的,它存在着不足,其不足就在于无法解决循环引用的问题,所以python中的gc就是来干这个事情的,通过分代技术根据对象的生命周期划分为三个链表,然后通过三色标记模型来找出那些具有循环引用的对象,改变它们的引用计数。所以在python中一个对象是否要被回收,最终还是取决于它的引用计数是否为0。

所以如果让你简述一下python的垃圾回收机制,你就可以回答:引用计数为主,分代技术为辅。

因此我们在做类的扩展的时候,这些问题就必须由我们来考虑了。

编写扩展类前奏曲

我们之前编写了扩展函数,我们说首先要创建一个模块,这里也是一样的。因为类也要在模块里面,编写函数是有套路的,编写类也是一样。我们还是先看看大致的流程,具体细节会在编写扩展类的时候介绍。

  • 类名、构造函数、析构函数
  • PyTypeObject,我们说类也是一个对象,它们都是一个PyTypeObject实例
  • PyType_Ready,初始化
  • PyModule_AddObject,将扩展类添加进模块中

PyTypeObject实例化一个类之后,我们是需要设置一些属性的,假设叫my_class吧,注意这个my_class只是C中的一个变量名,它和python中的类没有什么关系。那么我们需要设置如下属性:

  • my_class.ob_base = { PyObject_HEAD_INIT(&PyBaseObject_Type) 0 },这个写法比较固定,表示设置基类以及头部信息
  • my_class.tp_name = "MyClass";,类名,但是注意:这个名字不对应类,只是用来显示。比如你在python中,A = type("B", (object, ), {}),此时创建的类的名字就叫做B,对应这里的tp_name,但是使用的时候是通过A来使用的。不过从开发的角度来讲,我们认为名字应该是保持一致的。这个是必须要设置的,一个类要有名字。
  • my_class.tp_basicsize = sizeof(MyClass),一个类要有大小,这里的MyClass则需要单独定义了
  • my_class.tp_itemsize = 0;,元素的大小,这里是0,表示固定大小,因为我们的类是固定的
  • Py_TPFLAGS_HEAPTYPE:表示对象自己在堆中分配空间。Py_TPFLAGS_BASEYPE:是否可以被继承

从零开始创建一个类

#include "Python.h"


//懵逼的话,先看下面的代码
typedef struct{
    PyObject_HEAD //头部信息 
}MyClass; 

//这里我们实现python中的__new__方法,这个__new__方法接收哪些参数来着
//一个类本身,以及__init__中的参数,我们一般会这样写def __new__(cls, *args, **kwargs):
//所以这里的第一个参数就不再是PyObject *了,而是PyTypeObject *
static PyObject *
MyClass_new(PyTypeObject *cls, PyObject *args, PyObject *kw)
{
    //我们说python中的__new__方法默认都干了哪些事来着
    //为创建的实例对象开辟一份空间,然后会将这份空间的指针返回回去交给self
    //当然交给__init__的还有其它的参数,这些参数是__init__需要使用的,__new__方法不需要关心
    //但是毕竟要先经过__new__方法,所以__new__方法中要有参数位能够接收
    //最终__new__会将自身返回的self连同其它参数组合起来一块交给__init__
    //所以__init__中self我们不需要关心,我们只需要传递self后面的参数即可,因为在__new__会自动传递self
    //另外多提一嘴:我们使用实例对象调用方法的时候,会自动传递self,你有没有想过它为什么会自动传递呢?
    //其实这个在底层是使用了描述符,至于底层是怎么实现的,我们有机会的话,会单独开一篇博客来分析
    
    //所以我们这里要为self分配一个空间,self也是一个指针,但是它已经有了明确的类型,所以我们需要转化一下
    //当然这里不叫self也是可以的,只是我们按照官方的约定,不会引起歧义
    //分配空间是通过调用PyTypeObject的tp_alloc方法,传入一个PyTypeObject *,以及大小,这里是固定的所以是0
    MyClass *self = (MyClass *)cls -> tp_alloc(cls, 0);  //此时就由python管理了
    //记得返回self,转成PyObject *,当然我们这里的__new__方法的默认实现,你也可以做一些其它的事情来控制一下类的实例化行为
    return (PyObject *)self;
}

//构造函数接收三个PyObject *
static int 
MyClass_init(PyObject *self, PyObject *args, PyObject *kw)
{   
    //假设这个构造函数接收三个参数:name,age,gender
    char *name;
    int age;
    char *gender;
    char *keys[] = {"name", "age", "gender", NULL};
    if (!PyArg_ParseTupleAndKeywords(args, kw, "sis", keys, &name, &age, &gender)){
        //结果为0返回成功,结果为-1返回失败,这里失败了不能返回NULL,而是返回-1,__init__比较特殊
        return -1;
    }
    //至于如何设置到self当中,我们后面演示,这里先打印一下
    printf("name = %s, age = %d, gender = %s
", name, age, gender);
    
    //结果为0返回成功,结果为-1返回失败
    return 0;
}

void 
MyClass_del(PyObject *self)
{   
    //打印一句话吧
    printf("%s
", "call __del__");
    //拿到类型,调用tp_free释放
    Py_TYPE(self) -> tp_free(self);
}



static PyModuleDef HANSER = {
    PyModuleDef_HEAD_INIT, //头部信息
    "hanser",  //模块名
    "this is a module named hanser", //模块注释
    -1,  //模块空间
    0,  //这里是PyMethodDef数组的首个元素的地址,但是我们这里没有PyMethodDef,所以就是0
    NULL,
    NULL,
    NULL,
    NULL
};


PyMODINIT_FUNC
PyInit_hanser(void)
{   
    //创建类的这些过程,我们也可以单独写在一个函数中,我们这里第一次演示就直接写在模块初始化函数里面了
    
    
    //实例化一个PyTypeObject,但是这里面的属性非常多,我们通过直接赋值的方式需要写一大堆
    //所以先定义,然后设置指定的属性
    static PyTypeObject my_class;
    //我们知道PyTypeObject结构体的第一个参数就是PyVarObject ob_base;,需要引用计数(初始为1),类型&PyType_Type,ob_size(不可变,写上0即可)
    PyVarObject ob_base = {1, &PyType_Type, 0};
    my_class.ob_base = ob_base; //类的公共信息
    my_class.tp_name = "MyClass";  //类名
    //类的大小,这个MyClass就是我们上面定义的结构体的名字,也是在python中调用类所使用的名字
    //假设上面定义的是MyClass1,那么在python中你就需要使用MyClass1来实例化,但是使用type查看的时候显示的MyClass,因为类名叫MyClass
    //因此在开发中两个名字要保持一致
    my_class.tp_basicsize = sizeof(MyClass); 
    my_class.tp_itemsize = 0; //固定大小
    //我们说在python中创建一个类的实例,会调用__new__方法,所以我们也需要手动实现
    my_class.tp_new = MyClass_new;
    //构造函数__init__
    my_class.tp_init = MyClass_init;
    //析构函数
    my_class.tp_dealloc = MyClass_del;
    
    //初始化类,调用PyType_Ready。
    //而且python内部的类在创建完成之后也会调用这个方法进行初始化,它会对创建类进行一些属性的设置
    //记得传入指针进去
    if (PyType_Ready(&my_class) < 0){
        //如果结果小于0,说明设置失败
        return NULL;
    }
    //这个是我们自己创建的类,所以需要手动增加引用计数
    Py_XINCREF(&my_class);
    
    //加入到模块中,这个不需要在创建PyModuleDef的时候指定,而是可以单独添加
    //我们需要先把模块创建出来
    PyObject *m = PyModule_Create(&HANSER);
    //方式是:传入根据PyModuleDef *创建对象,也就是我们的模块、类名(这个类名要和我们上面设置的tp_name保持一致)、以及由PyTypeObject *转化得到的PyObject *
    //另外多提一嘴,这里的m、和my_class以及上面HANSER都只是C中变量,具体的模块名和类名是hanser和MyClass
    PyModule_AddObject(m, "MyClass", (PyObject *)&my_class);
    return m;
}
import hanser


# 然后实例化一个类
try:
    # 我们说这个类的构造函数中接收三个参数,尽管我们没有设置,但是该传还是要传的
    self = hanser.MyClass()
except Exception as e:
    print(e)
# 尽管实例化失败,但是这个对象在__new__方法中被创建了
# 所以依旧会调用__del__
"""
call __del__
Required argument 'name' (pos 1) not found
"""

# 传递参数,但是没有设置到self里面去
self = hanser.MyClass("mashiro", 16, "female")
# 打印
"""
name = mashiro, age = 16, gender = female
"""
# 调用析构函数
del self
"""
call __del__
"""

此刻我们就实现了在C中定义一个类,不过可能有人对这个PyTypeObject内部的成员不是很熟悉,我们再单独拿出来分析一下,并且把我们上面的例子再改一下。

PyTypeObject的内部成员

//我们之前是先实例化一个PyTypeObject对象
//然后再通过.的方式设置指定的成员,因为我们说直接赋值的话需要写很多东西
//我们看貌似是有些多,但是有时候我们也会直接这样实例化
//下面我们来介绍一下内部成员都代表什么含义
typedef struct _typeobject {
    //头部信息,PyVarObject ob_base; 里面包含了引用计数、类型、ob_size
    //关于设置,python提供了一个宏,PyVarObject_HEAD_INIT(type, size)
    //传入类型和大小可以直接创建,至于引用计数则默认为1
    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 */
    
    //析构方法__del__,当删除实例对象时会调用这个操作
    //typedef void (*destructor)(PyObject *); 函数接收一个PyObject *,没有返回值
    destructor tp_dealloc;
    
    //打印其实例对象是调用的函数
    //typedef int (*printfunc)(PyObject *, FILE *, int); 函数接收一个PyObject *、FILE *和int
    printfunc tp_print;
    
    //获取属性,内部的__getattr__方法
    //typedef PyObject *(*getattrfunc)(PyObject *, char *);
    getattrfunc tp_getattr;
    
    //设置属性,内部的__setattr__方法
    //typedef int (*setattrfunc)(PyObject *, char *, PyObject *);
    setattrfunc tp_setattr;
    
    //在python3.5之后才产生的,这个不需要关注。
    //并且在其它类的注释中,这个写的都是tp_reserved
    PyAsyncMethods *tp_as_async; /* formerly known as tp_compare (Python 2)
                                    or tp_reserved (Python 3) */
    //内部的__repr__方法
    //typedef PyObject *(*reprfunc)(PyObject *);
    reprfunc tp_repr;
    
    //一个对象作为数值所有拥有的方法
    PyNumberMethods *tp_as_number;
    //一个对象作为序列所有拥有的方法
    PySequenceMethods *tp_as_sequence;
    //一个对象作为映射所有拥有的方法
    PyMappingMethods *tp_as_mapping;

    /* More standard operations (here for binary compatibility) */
    
    //内部的__hash__方法
    //typedef Py_hash_t (*hashfunc)(PyObject *);
    hashfunc tp_hash;
    
    //内部的__call__方法
    //typedef PyObject * (*ternaryfunc)(PyObject *, PyObject *, PyObject *);
    ternaryfunc tp_call;
    
    //内部的__repr__方法
    //typedef PyObject *(*reprfunc)(PyObject *);
    reprfunc tp_str;
    
    //获取属性
    //typedef PyObject *(*getattrofunc)(PyObject *, PyObject *);
    getattrofunc tp_getattro;
    //设置属性
    //typedef int (*setattrofunc)(PyObject *, PyObject *, PyObject *);
    setattrofunc tp_setattro;
    
    //作为缓存,不需要关心
    /*
    typedef struct {
         getbufferproc bf_getbuffer;
        releasebufferproc bf_releasebuffer;
    } PyBufferProcs;
    */
    PyBufferProcs *tp_as_buffer;

    //这个类的特点,比如:
    //Py_TPFLAGS_HEAPTYPE:是否在堆区申请空间
    //Py_TPFLAGS_BASETYPE:是否允许这个类被其它类继承
    //Py_TPFLAGS_IS_ABSTRACT:是否为抽象类
    //Py_TPFLAGS_HAVE_GC:是否被垃圾回收跟踪
    //这里面有很多,具体可以去object.h中查看
    //一般我们设置成Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC即可
    unsigned long tp_flags;
    
    //这个类的注释
    const char *tp_doc; /* Documentation string */
    
    //用于检测是否出现循环引用,和下面的tp_clear是一组
    /*
    class A:
        pass
    a = A()
    a.attr = a
    此时就会出现循环引用
    */
    //typedef int (*traverseproc)(PyObject *, visitproc, void *);
    traverseproc tp_traverse;

    //删除对包含对象的引用
    inquiry tp_clear;

    //富比较
    //typedef PyObject *(*richcmpfunc) (PyObject *, PyObject *, int);
    richcmpfunc tp_richcompare;

    //弱引用,不需要关心
    Py_ssize_t tp_weaklistoffset;

    //__iter__方法
    //typedef PyObject *(*getiterfunc) (PyObject *);
    getiterfunc tp_iter;
    //__next__方法
    //typedef PyObject *(*iternextfunc) (PyObject *);
    iternextfunc tp_iternext;

    /* Attribute descriptor and subclassing stuff */
    //内部的方法,这个PyMethodDef不陌生了吧
    struct PyMethodDef *tp_methods;
    //内部的成员
    struct PyMemberDef *tp_members;
    //一个结构体,包含了name、get、set、doc、closure
    struct PyGetSetDef *tp_getset;
    
    //继承的基类
    struct _typeobject *tp_base;
    
    //内部的属性字典
    PyObject *tp_dict;
    
    //描述符,__get__方法
    //typedef PyObject *(*descrgetfunc) (PyObject *, PyObject *, PyObject *);
    descrgetfunc tp_descr_get;
    
    //描述符,__set__方法
    //typedef int (*descrsetfunc) (PyObject *, PyObject *, PyObject *);
    descrsetfunc tp_descr_set;
    
    //生成的实例对象是否有属性字典
    //我们上一个例子中的实例对象显然是没有属性字典的,因为我们当时没有设置这个成员
    Py_ssize_t tp_dictoffset;
    
    //初始化函数
    //typedef int (*initproc)(PyObject *, PyObject *, PyObject *);
    initproc tp_init;
    
    //为实例对象分配空间的函数
    //typedef PyObject *(*allocfunc)(struct _typeobject *, Py_ssize_t);
    allocfunc tp_alloc;
    
    //__new__方法
    //typedef PyObject *(*newfunc)(struct _typeobject *, PyObject *, PyObject *);
    newfunc tp_new;
    //我们一般设置到tp_new即可,剩下的就不需要管了
    
    
    
    //释放一个实例对象
    //typedef void (*freefunc)(void *); 一般会在析构函数中调用
    freefunc tp_free; /* Low-level free-memory routine */
    
    //typedef int (*inquiry)(PyObject *); 是否被gc跟踪
    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;
    unsigned int tp_version_tag;
    destructor tp_finalize;
} PyTypeObject;

这里面我们看到有很多成员,如果有些成员我们不需要的话,那么就设置为0即可。不过即便设置为0,但是有些成员我们在调用PyType_Ready初始化的时候,也会设置进去。比如tp_dict,这个我们创建类的时候没有设置,但是这个类是有属性字典的,因为在PyType_Ready中设置了;但有的不会,比如tp_dictoffset,这个我们没有设置,那么类在PyType_Ready中也不会设置,因此这个类的实例对象,就真的没有属性字典了。再比如tp_free,我们也没有设置,但是是可以调用的,原因你懂的。

使用python源码的方式创建

下面我们就来按照python源码创建内建的类的方式,来创建一个python中的类。

#include "Python.h"


typedef struct{
    PyObject_HEAD 
}MyClass; 

static PyObject *
MyClass_new(PyTypeObject *cls, PyObject *args, PyObject *kw)
{
    MyClass *self = (MyClass *)cls -> tp_alloc(cls, 0);  //此时就由python管理了
    return (PyObject *)self;
}

static int 
MyClass_init(PyObject *self, PyObject *args, PyObject *kw)
{   
    char *name;
    int age;
    char *gender;
    char *keys[] = {"name", "age", "gender", NULL};
    if (!PyArg_ParseTupleAndKeywords(args, kw, "sis", keys, &name, &age, &gender)){
        return -1;
    }
    printf("name = %s, age = %d, gender = %s
", name, age, gender);
    
    return 0;
}

void 
MyClass_del(PyObject *self)
{   
    Py_TYPE(self) -> tp_free(self);
}

//增加一个__call__方法
static PyObject *
MyClass_call(PyObject *self, PyObject *args, PyObject *kw)
{
    return PyUnicode_FromString("__call__方法被调用啦~~~");
}

static PyTypeObject my_class = {
    PyVarObject_HEAD_INIT(&PyType_Type, 0)
    "MyClass",                                  /* tp_name */
    sizeof(MyClass),                            /* tp_basicsize */
    0,                                          /* tp_itemsize */
    MyClass_del,                                /* tp_dealloc */
    0,                                          /* tp_print */
    0,                                          /* tp_getattr */
    0,                                          /* tp_setattr */
    0,                                          /* tp_reserved */
    0,                                          /* tp_repr */
    0,                                          /* tp_as_number */
    0,                                          /* tp_as_sequence */
    0,                                          /* tp_as_mapping */
    0,                                          /* tp_hash */
    MyClass_call,                               /* tp_call */
    0,                                          /* tp_str */
    0,                                          /* tp_getattro */
    0,                                          /* tp_setattro */
    0,                                          /* tp_as_buffer */
    0,                                          /* tp_flags */
    "this is a class",                          /* tp_doc */
    0,                                          /* tp_traverse */
    0,                                          /* tp_clear */
    0,                                          /* tp_richcompare */
    0,                                          /* tp_weaklistoffset */
    0,                                          /* tp_iter */
    0,                                          /* tp_iternext */
    0,                                          /* tp_methods */
    0,                                          /* tp_members */
    0,                                          /* tp_getset */
    0,                                          /* tp_base */
    0,                                          /* tp_dict */
    0,                                          /* tp_descr_get */
    0,                                          /* tp_descr_set */
    0,                                          /* tp_dictoffset */
    MyClass_init,                               /* tp_init */
    0,                                          /* tp_alloc */
    MyClass_new,                                /* tp_new */
    0,                                          /* tp_free */
};


static PyModuleDef HANSER = {
    PyModuleDef_HEAD_INIT, 
    "hanser",  
    "this is a module named hanser", 
    -1,  
    0,  
    NULL,
    NULL,
    NULL,
    NULL
};


PyMODINIT_FUNC
PyInit_hanser(void)
{   
    if (PyType_Ready(&my_class) < 0){
        return NULL;
    }
    Py_XINCREF(&my_class);
    
    PyObject *m = PyModule_Create(&HANSER);
    PyModule_AddObject(m, "MyClass", (PyObject *)&my_class);
    return m;
}
import hanser


self = hanser.MyClass("mashiro", 16, "female")
"""
name = mashiro, age = 16, gender = female
"""
print(hanser.MyClass.__doc__)  # this is a class
print(self())  # __call__方法被调用啦~~~

因此我们就再次实现了在C中创建一个python的类,并且目前是没有内存泄露的。

创建PyTypeObject模板

创建一个PyTypeObject对象我们可以就按照python源码中显示的来。

static PyTypeObject xxx = {
    PyVarObject_HEAD_INIT(&PyType_Type, 0)
    "xxx",                                       /* tp_name */
    sizeof(xxx),                                /* tp_basicsize */
    0,                                           /* tp_itemsize */
    0,                                           /* tp_dealloc */
    0,                                            /* tp_print */
    0,                                            /* tp_getattr */
    0,                                           /* tp_setattr */
    0,                                            /* tp_reserved */
    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 */
    0,                                          /* tp_flags */
    0,                                          /* tp_doc */
    0,                                          /* tp_traverse */
    0,                                          /* tp_clear */
    0,                                          /* tp_richcompare */
    0,                                          /* tp_weaklistoffset */
    0,                                          /* tp_iter */
    0,                                          /* tp_iternext */
    0,                                          /* tp_methods */
    0,                                          /* tp_members */
    0,                                          /* tp_getset */
    0,                                          /* tp_base */
    0,                                          /* tp_dict */
    0,                                          /* tp_descr_get */
    0,                                          /* tp_descr_set */
    0,                                          /* tp_dictoffset */
    0,                                          /* tp_init */
    0,                                          /* tp_alloc */
    0,                                         /* tp_new */
    0,                                          /* tp_free */
};

需要啥,就填啥即可。

给类添加成员和方法

添加成员

我们目前创建的类光秃秃的,啥也没有,下面我们就来使它变得饱满。

#include "Python.h"
#include "structmember.h"  //添加成员需要导入这个头文件


//添加成员,这里面的参数要和python中__init__中的参数保持一致
//你可以把name、age、gender看成是要通过self.的方式来设置的
//假设这里面没有gender,那么即使python中传了gender这个参数、并且解析出来了
//但是你没法设置,所以实例化的对象依旧无法访问
typedef struct{
    PyObject_HEAD 
    PyObject *name;
    PyObject *age;
    PyObject *gender;
}MyClass; 

//添加成员,这是一个PyNumberDef类型的数组,然后显然要把数组名放到类的tp_members中
//PyNumberDef结构体有以下成员:name type offset flags doc
static PyMemberDef members[] = {
    //这些成员具体值是什么?我们需要在MyClass_init中设置
    {
        "name", //成员名
        T_OBJECT_EX, //类型,关于类型我们只需要记住以下几种:T_INT T_FLOAT T_DOUBLE T_STRING T_OBJECT_EX T_CHAR
        //接收结构体对象和一个成员
        offsetof(MyClass, name), //对应值的偏移地址,由于python中的类是动态变化的,所以C只能通过偏移的地址来找到对应的成员,offsetof是一个宏
        0, //变量的读取类型,设置为0表示可读写,还可以可以设置成READONLY
        "this is a name" //成员说明
    },
    {"age", T_OBJECT_EX, offsetof(MyClass, age), 0, "this is a age"},
    {"gender", T_OBJECT_EX, offsetof(MyClass, gender), 0, "this is a gender"},
    //结尾有一个{NULL}
    {NULL}
};

static PyObject *
MyClass_new(PyTypeObject *cls, PyObject *args, PyObject *kw)
{   
    MyClass *self = (MyClass *)cls -> tp_alloc(cls, 0);  //此时就由python管理了
    return (PyObject *)self;
}

//构造函数接收三个PyObject *
static int 
MyClass_init(PyObject *self, PyObject *args, PyObject *kw)
{   
    char *name;
    int age;
    char *gender;
    char *keys[] = {"name", "age", "gender", NULL};
    if (!PyArg_ParseTupleAndKeywords(args, kw, "sis", keys, &name, &age, &gender)){
        return -1;
    }
    
    //这里就是设置__init__属性的,将解析出来的参数设置到__init__中,否则打印为None
    //注意PyObject *要转成MyClass *,并且考虑优先级,我们需要使用括号括起来
    ((MyClass *)self) -> name = PyUnicode_FromString(name);
    ((MyClass *)self) -> age = PyLong_FromLong(age);
    ((MyClass *)self) -> gender = PyUnicode_FromString(gender);
    
    //此时我们的构造函数就设置完成了
    return 0;
}

void 
MyClass_del(PyObject *self)
{   
    Py_TYPE(self) -> tp_free(self);
}

static PyObject *
MyClass_call(PyObject *self, PyObject *args, PyObject *kw)
{
    return PyUnicode_FromString("__call__方法被调用啦~~~");
}

static PyTypeObject my_class = {
    PyVarObject_HEAD_INIT(&PyType_Type, 0)
    "MyClass",                                  /* tp_name */
    sizeof(MyClass),                            /* tp_basicsize */
    0,                                          /* tp_itemsize */
    MyClass_del,                                /* tp_dealloc */
    0,                                          /* tp_print */
    0,                                          /* tp_getattr */
    0,                                          /* tp_setattr */
    0,                                          /* tp_reserved */
    0,                                          /* tp_repr */
    0,                                          /* tp_as_number */
    0,                                          /* tp_as_sequence */
    0,                                          /* tp_as_mapping */
    0,                                          /* tp_hash */
    MyClass_call,                               /* tp_call */
    0,                                          /* tp_str */
    0,                                          /* tp_getattro */
    0,                                          /* tp_setattro */
    0,                                          /* tp_as_buffer */
    0,                                          /* tp_flags */
    "this is a class",                          /* tp_doc */
    0,                                          /* tp_traverse */
    0,                                          /* tp_clear */
    0,                                          /* tp_richcompare */
    0,                                          /* tp_weaklistoffset */
    0,                                          /* tp_iter */
    0,                                          /* tp_iternext */
    0,                                          /* tp_methods */
    members,                                          /* tp_members */
    0,                                          /* tp_getset */
    0,                                          /* tp_base */
    0,                                          /* tp_dict */
    0,                                          /* tp_descr_get */
    0,                                          /* tp_descr_set */
    0,                                          /* tp_dictoffset */
    MyClass_init,                               /* tp_init */
    0,                                          /* tp_alloc */
    MyClass_new,                                /* tp_new */
    0,                                          /* tp_free */
};


static PyModuleDef HANSER = {
    PyModuleDef_HEAD_INIT, 
    "hanser",  
    "this is a module named hanser", 
    -1,  
    0,  
    NULL,
    NULL,
    NULL,
    NULL
};


PyMODINIT_FUNC
PyInit_hanser(void)
{   
    if (PyType_Ready(&my_class) < 0){
        return NULL;
    }
    Py_XINCREF(&my_class);
    
    PyObject *m = PyModule_Create(&HANSER);
    PyModule_AddObject(m, "MyClass", (PyObject *)&my_class);
    return m;
}
import hanser

cls = hanser.MyClass

self = cls("古明地觉", 16, "female")
print(self.name, self.age, self.gender)  # 古明地觉 16 female

# 但是一个比较神奇的地方是,我们使用类来调用不会报错,而是告诉我们这还少类的实例对象的一个成员
print(cls.name, cls.age, cls.gender)  # <member 'name' of 'MyClass' objects> <member 'age' of 'MyClass' objects> <member 'gender' of 'MyClass' objects>

添加方法

#include "Python.h"
#include "structmember.h" 


typedef struct{
    PyObject_HEAD 
    PyObject *name;
    PyObject *age;
    PyObject *gender;
}MyClass; 

//下面来给类添加方法啦,添加方法跟之前的创建函数是一样的
static PyObject *
age_incr_1(PyObject *self, PyObject *args, PyObject *kw)
{
    int age;
    char *keys[] = {"age", NULL};
    if (!PyArg_ParseTupleAndKeywords(args, kw, "i", keys, &age)){
        return NULL;
    }
    age++;
    ((MyClass *)self) -> age = PyLong_FromLong(age);
    return Py_None;
}


//构建PyMethodDef[], 方法和之前创建函数是一样的,但是这是类的方法,记得添加到类的tp_methods成员中
static PyMethodDef MyClass_methods[] = {
    {"age_incr_1", (PyCFunction)age_incr_1, METH_VARARGS | METH_KEYWORDS, "method age_incr_1"},
    {NULL, NULL}
};



static PyMemberDef members[] = {
    {
        "name", 
        T_OBJECT_EX, 
        offsetof(MyClass, name), 
        0, 
        "this is a name" 
    },
    {"age", T_OBJECT_EX, offsetof(MyClass, age), 0, "this is a age"},
    {"gender", T_OBJECT_EX, offsetof(MyClass, gender), 0, "this is a gender"},
    {NULL}
};

static PyObject *
MyClass_new(PyTypeObject *cls, PyObject *args, PyObject *kw)
{   
    MyClass *self = (MyClass *)cls -> tp_alloc(cls, 0); 
    return (PyObject *)self;
}


static int 
MyClass_init(PyObject *self, PyObject *args, PyObject *kw)
{   
    char *name;
    int age;
    char *gender;
    char *keys[] = {"name", "age", "gender", NULL};
    if (!PyArg_ParseTupleAndKeywords(args, kw, "sis", keys, &name, &age, &gender)){
        return -1;
    }
    
    ((MyClass *)self) -> name = PyUnicode_FromString(name);
    ((MyClass *)self) -> age = PyLong_FromLong(age);
    ((MyClass *)self) -> gender = PyUnicode_FromString(gender);
    
    return 0;
}

void 
MyClass_del(PyObject *self)
{   
    Py_TYPE(self) -> tp_free(self);
}

static PyObject *
MyClass_call(PyObject *self, PyObject *args, PyObject *kw)
{
    return PyUnicode_FromString("__call__方法被调用啦~~~");
}

static PyTypeObject my_class = {
    PyVarObject_HEAD_INIT(&PyType_Type, 0)
    "MyClass",                                  /* tp_name */
    sizeof(MyClass),                            /* tp_basicsize */
    0,                                          /* tp_itemsize */
    MyClass_del,                                /* tp_dealloc */
    0,                                          /* tp_print */
    0,                                          /* tp_getattr */
    0,                                          /* tp_setattr */
    0,                                          /* tp_reserved */
    0,                                          /* tp_repr */
    0,                                          /* tp_as_number */
    0,                                          /* tp_as_sequence */
    0,                                          /* tp_as_mapping */
    0,                                          /* tp_hash */
    MyClass_call,                               /* tp_call */
    0,                                          /* tp_str */
    0,                                          /* tp_getattro */
    0,                                          /* tp_setattro */
    0,                                          /* tp_as_buffer */
    0,                                          /* tp_flags */
    "this is a class",                          /* tp_doc */
    0,                                          /* tp_traverse */
    0,                                          /* tp_clear */
    0,                                          /* tp_richcompare */
    0,                                          /* tp_weaklistoffset */
    0,                                          /* tp_iter */
    0,                                          /* tp_iternext */
    MyClass_methods,                            /* tp_methods */
    members,                                    /* tp_members */
    0,                                          /* tp_getset */
    0,                                          /* tp_base */
    0,                                          /* tp_dict */
    0,                                          /* tp_descr_get */
    0,                                          /* tp_descr_set */
    0,                                          /* tp_dictoffset */
    MyClass_init,                               /* tp_init */
    0,                                          /* tp_alloc */
    MyClass_new,                                /* tp_new */
    0,                                          /* tp_free */
};


static PyModuleDef HANSER = {
    PyModuleDef_HEAD_INIT, 
    "hanser",  
    "this is a module named hanser", 
    -1,  
    0,
    NULL,
    NULL,
    NULL,
    NULL
};


PyMODINIT_FUNC
PyInit_hanser(void)
{   
    if (PyType_Ready(&my_class) < 0){
        return NULL;
    }
    Py_XINCREF(&my_class);
    
    PyObject *m = PyModule_Create(&HANSER);
    PyModule_AddObject(m, "MyClass", (PyObject *)&my_class);
    return m;
}
import hanser

cls = hanser.MyClass

self = cls("古明地觉", 16, "female")
print(self.age)  # 16

self.age_incr_1(self.age)
print(self.age)  # 17

以上我们就实现了如何在C中定义一个python的类,我们看到使用C来实现确实够麻烦的。所以我个人觉得,至少就我个人而言,一般不太会使用C来编写扩展模块,主要是太麻烦了,如果实现复杂的功能,那么你需要了解python底层大量的api,需要的代价太大了。如果需要效率,我个人更倾向于使用C来编写动态链接库的方式,然后使用ctypes去调用。

而且我们后面会介绍golang编写动态链接库交给python去调用,golang的效率虽然比不过C,但是也差不了多少,重点是golang带垃圾回收、而且开发速度上也比动态语言差不了多少。因此这篇博客更倾向于介绍python的底层,至于是否使用C来编写扩展模块,就由你自己来决定。而且使用C来编写,需要你有很强的C语言的知识,以及python源码的知识,我们目前这里只是介绍了很小的一部分。python中很多其它的东西在底层如何实现我们都没有介绍,比如:如何导入一个模块,如何在底层使用python的内置函数,python的生成器、列表解析怎么实现等等等等一大堆。这些需要你自己去了解了

因此是否使用扩展模块,我个人给出一个标准。如果你发现有大量的工作需要使用原生的C来实现才能保证高效率,但同时又需要和python直接交互,那么推荐你使用扩展模块的方式;如果你发现C没有做太多的事情,只是把Python创建函数、类的过程用底层重新翻译了一遍,或者说做了很多事情、但是不需要和python进行交互,我们可以使用ctypes获取返回的结果即可,那么完全没有必要使用扩展模块的方式。还是那句话,这篇博客只是一个引导作用,它不足以支持你在工作中编写扩展模块实现复杂的功能,更复杂的用法需要你自己再去了解。

因此折中的办法就是:你可以使用python编写,然后使用Cython加速,同样编译成python的pyd或者so,而且使用python编写效率会大大提高;还有就是使用C或者golang编写动态链接库,这样只需要你有静态语言的知识、不需要你有python源码的知识,只需要会使用ctypes调用即可。

类的循环引用

解决类的循环引用需要实现PyTypeObject的两个成员,tp_traverse(检测是否有循环引用)和tp_clear(清除引用计数)

static int
MyClass_traverse(PyObject *self, visitproc visit, void *args)
{
    //提供了一个宏,检测是否有循环引用
    Py_VISIT(PyObject *); //具体检测谁,根据代码情况
    //返回0表示成功
    return 0;
}

static int
MyClass_clear(PyObject *self)
{
    //同样提供了一个宏,进行引用计数的清理
    Py_CLEAR(PyObject *); //具体清理谁,根据代码情况
}

my_class -> tp_traverse = MyClass_traverse;
my_class -> tp_clear = MyClass_clear;

有兴趣可以"杀进"源码中自己查看。

以上です

以上是关于使用C语言为python编写动态模块--在C中实现python中的类的主要内容,如果未能解决你的问题,请参考以下文章

使用C语言为python编写动态模块--从底层深度解析python中的对象以及变量

在 C 中实现动态类型

python调用golang编写的动态链接库

c++中实现*字符前移

Elisp 11:动态模块

python上传模块,别人搜索不到