写python的c扩展简介
使用C/C++编写Python模块扩展
Python - 用C扩展编程
使用 C 或 C++ 扩展 Python
原因
- 添加额外的非python功能。
- 性能瓶颈的效率提升
- 专有源代码保密
写扩展库的代码
Extest.c文件包含包含要扩展的C模块,包含fac()和reverse()函数。
并调试完bug。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int fac(int n)
{
if (n<2) return 1;
return n * fac(n-1);
}
char * reverse(char *s)
{
register char t, *p = s, *q = s+(strlen(s)-1);
while(p<q){
t = *p;
*p++ = *q;
*q-- = t;
}
return s;
}
int test()
{
char s[255];
printf("4! == %d\n", fac(4));
printf("8! == %d\n", fac(8));
printf("12! == %d\n", fac(12));
strcpy(s, "abcdef");
printf("reversing 'abcdef', we get '%s'\n",reverse(s));
strcpy(s, "madam");
printf("reversing 'madam', we get '%s'\n",reverse(s));
return 0;
}
包装代码
- 包含python头文件
- 为每个模块的每个函数增加一个Pyobject * Module_func()的包装函数
- 为每个模块增加一个PyMethodDef ModuleMethods[]的数组
- 增加木块初始化函数void initModule()
为想被Python环境访问的函数增加一个静态变量,函数的返回值类型为PyObject*,函数名前要加上模块名和一个下划线。比如Extest模块的fac函数,创建包装函数Extest_fac()。这样可以在python中import Extest,然后调用Extest.fac()。
包装函数就是把Python的值传递给C,然后调用C函数处理。当处理完成返回给python的时候,把函数的计算结果转换成python对象,返回给python。
从python到C的转换用PyArg_Parse*系列函数。从C转到python的时候,就用Py_BuildValue()函数。
PyArg_Parse系列函数用法跟C的sscanf 函数很像,接受一个字符串流,根据一个指定格式字符串进行解析,把结果放入到相应的指针所指的变量中。返回值为1表示解析成功,返回值为0表示失败。
Py_BuildValue的用法跟sprintf函数很像,把所有的参数按格式字符串所指定的格式转换成一个python对象。
函数 | 描述 |
---|---|
int PyArg_ParseTuple() | 把python传过来的参数转为C |
int PyArg_ParseTupleAndKeywords() | 把python传过来的参数转为C,但是同时解析关键字参数 |
PyObject *Py_BuildValue() | 把C的数据转为python的一个对象或一组对象,然后返回 |
python和C数据转换的格式符号
format code | python type | c type |
---|---|---|
s | str | char * |
z | str/None | char * /NULL |
i | int | int |
l | long | long |
c | str | char |
d | float | double |
D | complex | Py_Complex * |
O | (any) | PyObject * |
S | str | PyStringObject |
// 包装函数
static PyObject * Extest_fac(PyObject * self, PyObject * args)
{
int res;
int num;
PyObject * retval;
res = PyArg_ParseTuple(args, "i", &num);
if (!res){
return NULL;
}
res = fac(num);
retval = Py_BuildValue("i", res);
return retval;
}
static PyObject * Extest_doppel(PyObject *self, PyObject * args)
{
char * orig_str;
char * dupe_str;
PyObject * retval;
if(!PyArg_ParseTuple(args, "s", $orig_str)) return NULL;
retval = Py_BuildValue("ss", orig_str, dupe_str=reverse(strdup(orig_str)));
free(dupe_str);
return retval;
}
static PyObject * Extest_test(PyObject * self, PyObject *args)
{
test();
return Py_BuildValue("");
}
‘ss’格式让Py_BuildValue()函数生成了一个包含两个字符串的tuple。
C代码中注意内存泄露。复制字符串后在函数退出前要释放。
为每个模块增加PyMethodDef ModuleMethods[]数组
完成包装函数后,需要把函数列在某个地方,编译python解释器能够导入并调用。
这是个二维数组,里面每个数组包含一个函数,最后放一个NULL数组表示列表的结束。
// 模块的方法数组PyMethodDef ModuleMethods[]
//METH_VARARGS常量表示参数以tuple形式传入.如果使用PyArg_ParseTupleAndKeywords()函数解析命名
//参数需要使用METH_VARARGS与METH_KEYWORDS常量进行逻辑与运算。
static PyMethodDef ExtestMethods[]{
{"fac", Extest_fac, METH_VARARGS},
{"doppel", Extest_doppel, METH_VARARGS},
{'test', Extest_test, METH_VARARGS}
{NULL, NULL},
}
增加模块初始换函数void initModule()
调用Py_InitModule()函数,并把模块名和ModuleMethods[]数组的名字穿进去。
//增加模块初始化函数void initModule()
void initExtest()
{
Py_InitModule("Extest", ExtestMethods);
}
编译
- 创建setup.py
- 通过运行setup.py来编译和连接代码
- 从python中导入扩展模块
创建setup.py,编译主要由setup()函数完成。这个函数调用之前的所有代码。每个扩展模块需要一个Extension实例。
Extension(MOD,sources=['Extest.c'])
名字为模块名,sources参数是所有源代码的文件列表。
setup()函数第一个参数表示要编译哪些东西,一个列表列出要编译的对象。
from distutils.core import setup, Extension
MOD = 'Extest'
setup(name=MOD, ext_modules=[Extension(MOD,sources=['Extest.c'])])
编译和连接代码
运行setup.py build命令可以开始编译我们的扩展了。
$python setup.py build
导入和测试
扩展会被创建在运行setup.py脚本所在的目录下的build/lib.*目录下。可以切换到哪个目录中测试新木块,也可以使用命令安装到python中
$python setup.py install
然后就可以在python中导入和使用模块中的函数了。