本节使用一个简单的例子引出Python C/C++ API的详细使用方法。针对的是CPython的解释器。
目标:创建一个Python内建模块test,提供一个功能函数distance, 计算空间中两个点之间的距离。
可以在Python代码这样使用test模块:
import test s = test.distance((0, 0, 0), (1, 1, 1))
先上代码:
[test.c]
#include <Python.h> #include <math.h> static PyObject* distance(PyObject* self, PyObject* args) { double x0, y0, z0, x1, y1, z1; if (!PyArg_ParseTuple(args, "(ddd)(ddd)", &x0, &y0, &z0, &x1, &y1, &z1)) { return NULL; } return PyFloat_FromDouble(sqrt((x0 - x1) * (x0 - x1) + (y0 - y1) * (y0 - y1) + (z0 - z1) * (z0 - z1))); } static PyMethodDef cformula_methods[] = { { "distance", distance, METH_VARARGS, "Return the 2D Distance of 2 Points." }, { NULL, NULL, 0, NULL }, }; PyMODINIT_FUNC inittest(void) { Py_InitModule3("test", cformula_methods, "Common test Written in C."); }
[Source.cpp]
#include <Python.h> PyMODINIT_FUNC inittest(); int main() { //PyImport_AppendInittab("test", inittest); Py_Initialize(); inittest(); PyRun_SimpleString("import test"); PyRun_SimpleString("s = test.distance((0, 0, 0), (1, 1, 1))"); PyRun_SimpleString("print s"); return 0; }
编译运行后,结果如下:
如果希望将test模块打包为一个动态链接库,供其他Python程序使用,即打包为test.pyd,在其他Python程序中可以直接import test,就和正常的Python内建模块一样。
步骤如下:
1) 在test.c同级目录下,新建一个python文件setup.py
2)setup.py:
from distutils.core import setup, Extension setup(ext_modules=[Extension('test', sources = ['test.c'])])
3) 执行python命令: python setup.py build
这种情况是因为没有指定C的编译器,本文使用VS2015提供的编译器,所以首先执行:SET VS90COMNTOOLS=%VS140COMNTOOLS%
会在该目录下发现build/lib.win32-2.7中有一个test.pyd,这就是编译后的动态库,可以直接import
实现细节:
1. 任何扩展Python模块的C程序,一般只需要包含<Python.h>头文件即可,文档中这样描述:
All function, type and macro definitions needed to use the Python/C API are included in your code by the following line:
#include "Python.h"
在包含了Python.h之后,隐含地会自动包含C语言的标准头文件<stdio.h>
, <string.h>
, <errno.h>
, <limits.h>
, <assert.h>
and <stdlib.h>
2. 本质上Python C API提供了对C函数的wrapper。假设有一个现成的C函数, int add(int a, int b), 想把它实现在Python的模块里,需要实现一个wrapper函数 static PyObject* addAB(PyObject* self, PyObject* args), 将args解析为两个整数a、b,然后调用add(a,b),将返回值打包为一个Python整型对象返回。
static PyObject* distance(PyObject* self, PyObject* args)
函数一定要声明为static,将其限定在此文件范围;参数self是Python内部使用的,遵循范式即可;参数args是函数的参数列表,是一个tuple。
PyArg_ParseTuple(args, "(ddd)(ddd)", &x0, &y0, &z0, &x1, &y1, &z1)
此函数将参数列表args解析为两个tuple, 每个tuple是三个double类型的元素。如果参数列表不符合"(ddd)(ddd)"这种形式,直接返回NULL。
PyFloat_FromDouble
此函数将一个C原生的double,封装Python 的PyFloatObject。
3. 定义该模块对外提供的函数接口
static PyMethodDef cformula_methods[]
在此数据结构中定义模块test对外提供的函数接口,第一个参数"distance"是Python内部记录的,就是test.distance调用时,python查找的函数名;第二个参数distance是函数具体实现,本质是一个函数指针;第三个参数是METH_VARARGS告诉Python此函数一个典型的PyCFunction,参数为两个PyOBject*,参数2使用PyArg_ParseTuple来解析;最后一个是函数说明。
4.定义模块初始化函数
PyMODINIT_FUNC inittest(void) { Py_InitModule3("test", cformula_methods, "Common test Written in C."); }
函数inittest必须这样定义,如果模块名为example,那么模块初始化函数为initexample。宏定义PyMODINIT_FUNC定义如下:
# if defined(__cplusplus) # define PyMODINIT_FUNC extern "C" void # else /* __cplusplus */ # define PyMODINIT_FUNC void # endif /* __cplusplus */
针对cpp的实现加了修饰extern "C"。
Py_InitModule3创建模块test, 当import test时,Python解释器会找到inittest创建的模块test。
5. 测试代码Source.cpp
在Python虚拟机环境初始化 Py_Initialize()之后,调用inittest(),则会创建新模块test。
如果希望在import test时才初始化模块test,那么在Py_Initialize()之前调用PyImport_AppendInittab("test", inittest); 然后再注释掉initest()。
PyImport_AppendInittab:Add a single module to the existing table of built-in modules.The new module can be imported by the namename, and uses the function initfunc as the initialization function called on the first attempted import. This should be called before Py_Initialize()
.
本节通过一个简单的例子来展示了如何用C语言扩展Python模块,并简单解释了用到的Python C/C++ API。在下一节中,将更加深入地了解Python C/C++ API,以及在写扩展程序时,会遇到的坑,比如:异常处理、引用计数等等。