当python的基本功能无法满足要求,或者是为了保密源代码(.py)、遇到性能瓶颈时,我们常常要扩展python,扩展语言可以是C/C++、Java、C#等。
为python创建扩展需要三个主要的步骤:创建应用程序代码;利用样板来包装代码;编译与测试。
1、 创建应用程序代码
我们创建一个C代码PythonEx.c,实现两个函数fac()和reverse(),分别用来求阶乘和逆转字符串,test()函数是用来测试fac()和reverse()基本功能的,以防问题带入python。
// PythonEx.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int fac(int n)
{
if (n < 2) {
return (1);
}
else {
return (n) * fac(n - 1);
}
}
char* reverse(char *s)
{
register char t;
register char *p = s;
register char *q = (s + strlen(s) -1);
while (p < q) {
t = *p;
*p++ = *q;
*q-- = t;
}
return s;
}
int test()
{
char s[BUFSIZ];
printf("3! = %d\n", fac(3));
printf("6! = %d\n", fac(6));
printf("9! = %d\n", fac(9));
strcpy(s, "abcdefg");
printf("‘abcdefg‘ after reversing is ‘%s‘\n", reverse(s));
strcpy(s, "python");
printf("‘python‘ after reversing is ‘%s‘\n", reverse(s));
return 0;
}
2、 用样板包装代码
样板是扩展代码与python解释器之间进行交互的桥梁,主要分为以下4步。
a.包含python的头文件。
找到python头文件位置并确保编译器的访问权限,然后在代码中inlcude这个头文件,如下:
#include “Python.h”
b.为每个模块的每一个函数增加一个型如PyObject* Module_func()的包装函数。
这部分需要为所有想被python环境访问的函数都增加一个静态的函数,函数的返回值类型为PyObject*,函数名前面要加上模块名和一个下划线。包装函数的用处就是先把python的值传递给C,然后调用我们想要调用的相关函数,当这个函数完成要返回python的时候,把函数的计算结果转换成python的对象,然后返回给python。那么,在从python到C的转换就用PyArg_Parse*系列函数,在从C转到python的时候,就用Py_BuildValue()函数。
下面我们包装fac()和reverse()函数,假设其模块名为CustomPy,如下:
static PyObject* CustomPy_fac(PyObject *self, PyObject *args)
{
int num;
if (!PyArg_ParseTuple(args, "i", &num)) { // i i.e. int->int
return NULL;
}
return (PyObject*)Py_BuildValue("i", fac(num)); // i i.e. int->int
}
static PyObject* CustomPy_reverse(PyObject *self, PyObject *args)
{
char *orig_str; // original
char *dup_str; // reversed
PyObject *retval;
if (!PyArg_ParseTuple(args, "s", &orig_str)) { // s i.e. str->char*
return NULL;
}
retval = (PyObject*)Py_BuildValue(
"ss", // s i.e. char*->str
orig_str,
dup_str = reverse(strdup(orig_str))); // strdup
free(dup_str); // free after strdup
return retval; // return tuple(orig_str, dup_str)
}
static PyObject* CustomPy_test(PyObject *self, PyObject *args)
{
test();
return (PyObject*)Py_BuildValue("");
}
c.为每个模块增加一个型如PyMethodDef ModuleMethods[]的数组。
static PyMethodDef CustomPyMethods[] = {
{"fac", CustomPy_fac, METH_VARARGS},
{"reverse", CustomPy_reverse, METH_VARARGS},
{"test", CustomPy_test, METH_VARARGS},
{NULL, NULL},
};
完成包装函数后,把他们加入到一个数组中,以便于python解释器能够导入并调用它们。每一个数组都包含了函数在python中的名字、相应的包装函数的名字以及一个METH_VARARGS常量,这个常量表示参数以tuple形式传入。最后是一个NULL数组表示列表结束。
d.增加模块初始化函数void initModule()
void initCustomPy()
{
Py_InitModule("CustomPy", CustomPyMethods);
}
这部分代码在模块被导入的时候被解释器调用。
另外,创建扩展还可以先写包装代码,使用桩函数、测试函数或哑函数,在开发过程中慢慢地把这些函数用有实际功能的函数替换。
3、编译与测试
为了让新python扩展能被创建,需要把它们与python库放在一起编译,以前可能要用到Makefile,现在使用distutils模块就可以了,能够方便地编译、安装和分发这些模块、扩展和包,只需创建一个setup.py脚本就行。
#!/usr/bin/env python
from distutils.core import setup, Extension
MOD = ‘CustomPy‘
setup(name = MOD, ext_modules = [Extension(MOD, sources = [PythonEx.c])])
下面直接运行setup.py脚本来创建自定义模块,或者是install到python环境里,最终会生成一个so文件,接着import这个模块就可以使用了。
还有两点需要注意的是,python的垃圾自动回收策略是引用计数,以及线程安全操作,这些都可以加入到自定义的模块中。
上面的例子中用C扩展了Python,我们还可以用Java扩展Jython,使用C#或者VB .NET扩展IronPython。如果在win32下,python还可以使用其COM(市场名字为ActiveX)操作Microsoft Office。