• 使用C语言为python编写动态模块(3)--在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 *); //具体清理谁,根据代码情况
    }
    
    //另外我们在析构函数中,其实还少了一步
    void 
    MyClass_del(PyObject *self)
    {   
        //我们说python会跟踪创建的对象,如果被回收了,那么应该从链表中移除
        PyObject_GC_UnTrack(self);
        Py_TYPE(self) -> tp_free(self);
    }
    
    my_class -> tp_traverse = MyClass_traverse;
    my_class -> tp_clear = MyClass_clear;
    

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

    以上です

  • 相关阅读:
    AOP的实现原理——动态代理
    反射
    代理模式
    sprig——jar包
    spring事务管理
    [c++基础]3/5原则--拷贝构造函数+拷贝赋值操作符
    [c++面试准备]--vector对象是如何增长的
    c++面试须知
    redhat--1
    kvm配置虚拟机[待整理]
  • 原文地址:https://www.cnblogs.com/traditional/p/12283903.html
Copyright © 2020-2023  润新知