zoukankan      html  css  js  c++  java
  • 使用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;
    

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

    以上です

  • 相关阅读:
    JavaScript 为字符串添加样式 【每日一段代码80】
    JavaScript replace()方法 【每日一段代码83】
    JavaScript for in 遍历数组 【每日一段代码89】
    JavaScript 创建用于对象的模板【每日一段代码78】
    html5 css3 新元素简单页面布局
    JavaScript Array() 数组 【每日一段代码88】
    JavaScript toUTCString() 方法 【每日一段代码86】
    位运算
    POJ 3259 Wormholes
    POJ 3169 Layout
  • 原文地址:https://www.cnblogs.com/traditional/p/12283903.html
Copyright © 2011-2022 走看看