C扩展python的module和Type

有许多理由给CPython写扩展,比如 1.性能低 2.重复用别人的C/C++代码 3.在自己的程序中定制python 4.为了方便 等等。

写这种扩展其实都是套路,不过最好要有对CPython源码有点熟悉。

添加C完成的 module有两种方法

1.直接修改python源代码,写好自己的module。静态编译到CPython中去.

2.将module做成dll(pyd)文件,然后动态加载。

套路

Python代码调用C代码套路:

  1. 将被调用C函数包装成一个进PyCFunction函数,
  2. 在这个函数包装函数中,拆开python传进来的参数
  3. 调用目标C函数

C调用python代码套路:

  1. 将Python要用到参数做成PyTupleObject,PyDictObject
  2. 调用PyObject_Call(callable,tuple,dict)

这个过程中要注意各种ref_count增减规则,可能会导致内存泄漏,一般规则似乎是,

  1. 在创建对象的时候,各种new函数会增加ref_cnt,
  2. 在传递参数的时候,被调函数不会负责额外ref_cnt,但是不需要额外的inc,dec。
  3. 在作为返回值的时候,如果在调用函数中,需要把这个返回值存储到某个地方的,那么inc一次。

整个原则就是在我们需要额外存储一下的时候,inc一次,在作为参数传递或者返回值,但是不会被赋值到其他地方的,那么就不需要inc了

一、添加module

myext module,将下面这份C代码编译成myext.dll(pyd),放到python.exe目录

#include <Python.h>

static 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}
};

//初始化module
PyMODINIT_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,PyDictObject
2.调用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 myext

def 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 myext
import gc
import sys

from myext import student
s=student(grade=2,name='mike',school='thu',sex='male')
#s.name=s;
print("refcnt:",sys.getrefcount(s))
del s
gc.collect()

此处没有循环引用,结果

 

将s.name=s去掉注释,结果:

 

posted @ 2016-06-20 10:32  瘸腿  阅读(783)  评论(0编辑  收藏  举报