楔子
这次我们来介绍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;
有兴趣可以"杀进"源码中自己查看。