有许多理由给CPython写扩展,比如 1.性能低 2.重复用别人的C/C++代码 3.在自己的程序中定制python 4.为了方便 等等。
写这种扩展其实都是套路,不过最好要有对CPython源码有点熟悉。
添加C完成的 module有两种方法
1.直接修改python源代码,写好自己的module。静态编译到CPython中去.
2.将module做成dll(pyd)文件,然后动态加载。
套路
Python代码调用C代码套路:
- 将被调用C函数包装成一个进PyCFunction函数,
- 在这个函数包装函数中,拆开python传进来的参数
- 调用目标C函数
C调用python代码套路:
- 将Python要用到参数做成PyTupleObject,PyDictObject
- 调用PyObject_Call(callable,tuple,dict)
这个过程中要注意各种ref_count增减规则,可能会导致内存泄漏,一般规则似乎是,
- 在创建对象的时候,各种new函数会增加ref_cnt,
- 在传递参数的时候,被调函数不会负责额外ref_cnt,但是不需要额外的inc,dec。
- 在作为返回值的时候,如果在调用函数中,需要把这个返回值存储到某个地方的,那么inc一次。
整个原则就是在我们需要额外存储一下的时候,inc一次,在作为参数传递或者返回值,但是不会被赋值到其他地方的,那么就不需要inc了
一、添加module
myext module,将下面这份C代码编译成myext.dll(pyd),放到python.exe目录
#includestatic PyObject *myextError;static PyObject *callable;//static PyObject *moduleself;static PyObject* myext_system(PyObject *self, PyObject *args);static PyObject* myext_call(PyObject *self,PyObject *args,PyObject* kwargs);static PyMethodDef myextMethods[]={ {"system", myext_system, METH_VARARGS,"Execute a shell command."}, {"call",(PyCFunction)myext_call,METH_VARARGS|METH_KEYWORDS,"None"}, {NULL, NULL, 0, NULL}};//初始化modulePyMODINIT_FUNC initmyext(void){ PyObject *m; //创建PyModuleObject对象,存储到PyInterpreterState.modules中去 m = Py_InitModule("myext", myextMethods); if (m == NULL) return; //创建一个error class object myextError = PyErr_NewException("myext.error", NULL, NULL); Py_INCREF(myextError); //将error 添加到m.dict PyModule_AddObject(m, "error", myextError); }/*python代码调用C/C++代码,步骤1.将被调用C函数包装成一个进PyCFunction函数,2.在这个函数包装函数中,拆开python传进来的参数3.调用目标C函数*/static PyObject* myext_system(PyObject *self, PyObject *args){ const char *command; int sts; if (!PyArg_ParseTuple(args, "s", &command)) return NULL; sts = system(command); if (sts < 0) { PyErr_SetString(myextError, "System command failed"); return NULL; } return Py_BuildValue("i",sts);//这种虽然可以使用,但是效率显然低,PyLong_FromLong}/*C代码调用Python代码,步骤1.将Python要用到参数做成PyTupleObject,PyDictObject2.调用PyObject_Call(callable,tuple,dict)*/static PyObject* myext_call(PyObject *self,PyObject* args,PyObject* kwargs){ if ((args!=NULL&&!PyTuple_Check(args)) ||(kwargs!=NULL&&!PyDict_Check(kwargs)) ){ PyErr_SetString(PyExc_TypeError,"parameter type error"); return NULL; } Py_ssize_t size=PyTuple_Size(args); if (size<1){ PyErr_SetString(PyExc_Exception,"first parameter must be a callable object"); return NULL; } PyObject* callablePositionArgs=NULL; PyObject* callable=NULL; callable=PyTuple_GetItem(args,0); if (!PyCallable_Check(callable)){ PyErr_SetString(PyExc_Exception,"first parameter must be a callable object"); return NULL; } //切割tuple中剩余的值,作为 callable的参数 callablePositionArgs=PyTuple_GetSlice(args,1,size+1); return PyObject_Call(callable,callablePositionArgs,kwargs);}
python测试代码:
import myextdef fun(a,b,kw1='abc',kw2="abc"): print "a+b="+str(a+b),kw1,kw2 myext.call(fun,1,333,kw2='xyz')myext.system("ping www.weibo.com")raw_input()
测试
二、添加PyTypeObject
这个过程相当于给Python添加了新类型,我们从PyTypeObject开始。一个typeobject 就相当于一个类,通常(有列外id情况) Python vm根据这个typeobject的设置来创建 instance,设置一些规则函数和class特性flag,比如compare,iter,call,getset,hash之类的。不同的类有不同的状况,所以有不同的设置。
这个添加的过程感觉最复杂的地方还是Py_XINCREF和Py_XDECREF的部分,有点凌乱。
先解释几个PyTypeObject中的几个字段:
tp_new:这个要new出一个 Type对应的 instance对象来
student_init:这个相当于构造函数,现在越来越觉得构造函数应该叫初始化函数了,C++/C#/java中都应该这么称呼。
tp_methods,tp_members,tp_getset:这里的东西会转换设置到Type的dict/dictproxy中去
student_traverse:在gc.collect的时候回调,在这个函数中检测有可能出现循环引用的成员
student_clear:在gc发现有循环引用之后回调,这个函数的主要目的是用于断开循环引用
student_dealloc:在删除对象的时候回调
tp_basicsize:对象基本部分的大小
tp_itemsize:可变部分对象,每个item的大小。
tp_flags:Py_TPFLAGS_HAVE_GC这个标记在gc的时候才会用到,如果没这个标记应该是不会检测循环引用的。
另外有许多在PyType_Ready的时候会填上,它们大多是从object等等继承而来的。
举个新增类型的栗子:
#include "studenttype.h"static PyObject* info(StudentObject* self){ return Py_None;}static PyMemberDef Student_members[] = { {"school", T_OBJECT_EX, offsetof(StudentObject, school), 0,"school"}, {"grade", T_INT, offsetof(StudentObject, grade), 0,"grade"}, {"name", T_OBJECT_EX, offsetof(StudentObject, name), 0,"name"}, //{"sex",T_OBJECT,offsetof(StudentObject,sex),0,"sex"}, {NULL} /* Sentinel */};static PyObject* student_new(PyTypeObject* typeobj,PyObject* args,PyObject* kwds){ StudentObject* self=NULL; self=(StudentObject*)typeobj->tp_alloc(typeobj,0); if (self!=NULL){ Py_INCREF(Py_None); self->grade=0; } return (PyObject*)self;}static void student_dealloc(StudentObject* student){ if (student!=NULL && student->ob_type==&PyStudent_Type){ printf("student dealloc\n"); Py_XDECREF(student->name); Py_XDECREF(student->school); Py_XDECREF(student->sex); Py_CLEAR(student); if (student==NULL){ printf("removed\n"); } }}static int student_init(StudentObject* self, PyObject* args, PyObject* kwds){ PyObject *name=NULL,*school=NULL,*sex=NULL; PyObject* tmp; static char* kwlist[]={"grade","name","school","sex"}; if (!PyArg_ParseTupleAndKeywords(args, kwds, "|iSSS", kwlist, &self->grade, &name, &school, &sex)){ return -1; } if (name){ tmp=self->name; Py_XINCREF(name); self->name=name; Py_XDECREF(tmp); } if(school){ tmp=self->school; Py_XINCREF(school); self->school=school; Py_XDECREF(tmp); } if(sex){ tmp=self->sex; Py_XINCREF(sex); self->sex=sex; Py_XDECREF(tmp); } return 0;}//gc回调函数static int student_traverse(StudentObject *self, visitproc visit, void *arg){ if (self->ob_type==&PyStudent_Type){ printf("student_traverse\n"); Py_VISIT(self->name); Py_VISIT(self->school); Py_VISIT(self->sex); } return 0;}//在检测到有循环引用之后会调用这个函数static int student_clear(StudentObject* student){ printf("student clear1\n"); Py_CLEAR(student->name); Py_CLEAR(student->school); Py_CLEAR(student->sex); return 0;}static PyObject* getsex(StudentObject* self,void* closure){ printf("in getsex\n"); //Py_INCREF(self->sex); return self->sex; //Py_INCREF(Py_None); //return Py_None;}static int setsex(StudentObject* self,PyObject* value,void* closure){ if (value == NULL) { PyErr_SetString(PyExc_TypeError, "are you joking?"); return -1; } if (! PyString_Check(value)) { PyErr_SetString(PyExc_TypeError, "the sex attribute must be string"); return -1; } if (_PyString_Eq(value,PyString_FromString("male")) || _PyString_Eq(value,PyString_FromString("female"))){ Py_XDECREF(self->sex); Py_XINCREF(value); self->sex = value; }else{ PyErr_SetString(PyExc_TypeError, "sex type should be female or male"); return -1; } return 0;}static PyGetSetDef Student_getseters[]={ {"sex",(getter)getsex,(setter)setsex, "about sex",NULL}, {NULL}};static PyMethodDef Student_methods[]={ {"info", (PyCFunction)info, METH_NOARGS,"Return detail info of student"}, {NULL} /* Sentinel */};static PyTypeObject PyStudent_Type = { PyObject_HEAD_INIT(NULL) 0, /*ob_size*/ "student", /*tp_name*/ sizeof(StudentObject), /*tp_basicsize*/ 0, /*tp_itemsize*/ (destructor)student_dealloc, /*tp_dealloc 这个 del student的时候会回调到*/ 0, /*tp_print*/ 0, /*tp_getattr*/ 0, /*tp_setattr*/ 0, /*tp_compare*/ 0, /*tp_repr*/ 0, /*tp_as_number*/ 0, /*tp_as_sequence*/ 0, /*tp_as_mapping*/ 0, /*tp_hash */ 0, /*tp_call*/ 0, /*tp_str*/ 0, /*tp_getattro*/ 0, /*tp_setattro*/ 0, /*tp_as_buffer*/ Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE|Py_TPFLAGS_HAVE_GC, /*tp_flags*/ "student", /* tp_doc */ (traverseproc)student_traverse, /* tp_traverse */ (inquiry)student_clear, /* tp_clear 不明白这个怎么回事*/ 0, /* tp_richcompare */ 0, /* tp_weaklistoffset */ 0, /* tp_iter */ 0, /* tp_iternext */ Student_methods, /* tp_methods */ Student_members, /* tp_members */ Student_getseters, /* tp_getset */ 0, /* tp_base */ 0, /* tp_dict */ 0, /* tp_descr_get */ 0, /* tp_descr_set */ 0, /* tp_dictoffset */ (initproc)student_init, /* tp_init */ 0, /* tp_alloc */ student_new, /* tp_new */};PyTypeObject* initstudenttype(){ //PyStudent_Type.tp_new=PyType_GenericNew; if (PyType_Ready(&PyStudent_Type)){ return NULL; } return &PyStudent_Type;}
python测试代码:
import myextimport gcimport sysfrom myext import students=student(grade=2,name='mike',school='thu',sex='male')#s.name=s;print("refcnt:",sys.getrefcount(s))del sgc.collect()
此处没有循环引用,结果
将s.name=s去掉注释,结果: