Python 3 – 用C进行扩展编程
任何使用编译语言,如C、C++或Java编写的代码都可以集成或导入到另一个Python脚本中。这些代码被认为是“扩展”。
Python扩展模块不过是一个普通的C库。在Unix机器上,这些库通常以 .so (共享对象)结尾。在Windows机器上,通常是以 .dll (动态链接库)结尾。
编写扩展的先决条件
要开始编写扩展,你需要Python的头文件。
- 在Unix机器上,通常需要安装特定于开发人员的软件包,例如 python2.5-dev 。
-
Windows用户在使用二进制Python安装程序时将这些头文件作为软件包的一部分获取。
此外,假定你具有使用C编程编写任何Python扩展的良好知识。
首先看一下Python扩展
要查看Python扩展模块,你需要将你的代码分成四部分 –
- 头文件
Python.h
。 -
你要作为模块接口公开的C函数。
-
将Python开发人员在扩展模块中看到的函数名称映射为C函数的表。
-
初始化函数。
头文件Python.h
在C源文件中需要包含 Python.h
头文件,该文件给你访问内部Python API的权限,用于将你的模块挂钩到解释器中。
确保在任何其他必要的头文件之前包含Python.h。需要遵循使用你想要从Python调用的函数的头文件。
C函数
你的函数的C实现的签名总是有以下三种形式之一 –
static PyObject *MyFunction( PyObject *self, PyObject *args );
static PyObject *MyFunctionWithKeywords(PyObject *self, PyObject *args, PyObject *kw);
static PyObject *MyFunctionWithNoArgs( PyObject *self );
每个前面的声明都返回一个Python对象。在Python中没有像C中那样的 void 函数。如果不想让函数返回值,请返回Python的 None 值的C等效值。Python头文件定义了一个宏,Py_RETURN_NONE,为我们执行此操作。
你的C函数的名称可以随意设置,因为它们从未在扩展模块之外看到。它们被定义为 static 函数。
通常,你的C函数由组合Python模块和函数名称而命名,如下所示 –
static PyObject *module_func(PyObject *self, PyObject *args) {
/* 在这里执行你的操作。 */
Py_RETURN_NONE;
}
这是模块 module
中的一个Python函数 func
。你将把指向你的C函数的指针放入通常在你的源代码中接下来的模块的方法表中。
方法映射表
该方法表是一组简单的PyMethodDef结构的数组。该结构看起来像这样 –
struct PyMethodDef {
char *ml_name;
PyCFunction ml_meth;
int ml_flags;
char *ml_doc;
};
下面是此结构的成员描述 –
- ml_name − 这是Python解释器在使用Python程序时呈现的函数名称。
-
ml_meth − 这是具有前面一节中描述的任何一个签名的函数的地址。
-
ml_flags − 这告诉解释器ml_meth正在使用的三个签名中的哪一个。
- 此标志通常具有METH_VARARGS的值。
-
如果您想要允许关键字参数进入您的函数,则可以将此标志按位OR’ed METH_KEYWORDS。
-
还可以具有值METH_NOARGS,表示您不希望接受任何参数。
-
ml_doc − 这是函数的docstring,如果您不想编写docstring,则可以为NULL。
此表需要以对应成员的NULL和0值为终止的sentinel终止。
示例
对于上面定义的函数,我们有以下方法映射表-
static PyMethodDef module_methods[] = {
{ "func", (PyCFunction)module_func, METH_NOARGS, NULL },
{ NULL, NULL, 0, NULL }
};
初始化函数
您的扩展模块的最后部分是初始化函数。当加载模块时,Python解释器将调用此函数。需要将函数命名为 **init Module ** ,其中 Module 是模块的名称。
初始化函数需要从您将要构建的库中导出。 Python头文件定义了PyMODINIT_FUNC以包含特定环境中发生这种情况所需的适当咒语。定义函数时,您所要做的就是使用它。
您的C初始化函数通常具有以下总体结构−
PyMODINIT_FUNC initModule() {
Py_InitModule3(func, module_methods, "docstring...");
}
这是 Py_InitModule3 函数的描述−
- func − 这是要导出的函数。
-
module_methods − 这是上面定义的映射表名称。
-
docstring − 这是您想要在扩展中添加的注释。
将所有这些组合起来,看起来像下面这样−
#include <Python.h>
static PyObject *module_func(PyObject *self, PyObject *args) {
/* Do your stuff here. */
Py_RETURN_NONE;
}
static PyMethodDef module_methods[] = {
{ "func", (PyCFunction)module_func, METH_NOARGS, NULL },
{ NULL, NULL, 0, NULL }
};
PyMODINIT_FUNC initModule() {
Py_InitModule3(func, module_methods, "docstring...");
}
示例
一个简单的示例,它使用了所有以上概念−
#include <Python.h>
static PyObject* helloworld(PyObject* self)
{
return Py_BuildValue("s", "Hello, Python extensions!!");
}
static char helloworld_docs[] =
"helloworld( ): Any message you want to put here!!\n";
static PyMethodDef helloworld_funcs[] = {
{"helloworld", (PyCFunction)helloworld,
METH_NOARGS, helloworld_docs},
{NULL}
};
void inithelloworld(void)
{
Py_InitModule3("helloworld", helloworld_funcs, "Extension module example!");
}
在此示例中,使用 Py_BuildValue
函数构建一个Python值。将上述代码保存到hello.c文件中。我们将看到如何编译和安装此模块以从Python脚本调用。
构建和安装扩展
distutils
包使得以一种标准的方式分发Python模块(包括纯Python和扩展模块)变得非常容易。模块以源代码形式分发,通常使用名为 setup.py 的设置脚本进行构建和安装。
对于上述模块,您需要准备以下 setup.py
脚本:
from distutils.core import setup, Extension
setup(name = 'helloworld', version = '1.0', \
ext_modules = [Extension('helloworld', ['hello.c'])])
现在使用以下命令,执行所有必要的编译和链接步骤,使用正确的编译器和链接器命令和标志,并将生成的动态库复制到适当的目录中:
$ python setup.py install
在基于Unix的系统上,您很可能需要以root身份运行此命令,以便具有写入site-packages目录的权限。在Windows上,通常不会出现这个问题。
导入扩展
一旦安装了扩展,您就可以像以下示例那样在Python脚本中导入和调用扩展:
示例
#!/usr/bin/python3
import helloworld
print helloworld.helloworld()
输出
这将产生以下结果:
Hello, Python extensions!!
传递函数参数
由于您最有可能想要定义接受参数的函数,因此您可以使用C函数的另一种签名之一。例如,以下函数,它接受一些参数,将定义如下:
static PyObject *module_func(PyObject *self, PyObject *args) {
/* 解析参数并在此执行某些有趣的操作。*/
Py_RETURN_NONE;
}
包含新函数条目的方法表将如下所示:
static PyMethodDef module_methods[] = {
{ "func", (PyCFunction)module_func, METH_NOARGS, NULL },
{ "func", module_func, METH_VARARGS, NULL },
{ NULL, NULL, 0, NULL }
};
您可以使用API PyArg_ParseTuple 函数从传递到C函数的一个PyObject指针中提取参数。
PyArg_ParseTuple的第一个参数是args参数。这是您将进行 解析 的对象。第二个参数是格式字符串,描述您希望它们出现的参数。每个参数都由格式字符串中一个或多个字符表示,如下所示。
static PyObject *module_func(PyObject *self, PyObject *args) {
int i;
double d;
char *s;
if (!PyArg_ParseTuple(args, "ids", &i, &d, &s)) {
return NULL;
}
/* 在此执行一些有趣的操作。*/
Py_RETURN_NONE;
}
输出
编译您的模块的新版本并导入它后,您可以使用任何类型和数量的参数调用新函数。
module.func(1, s = "three", d = 2.0)
module.func(i = 1, d = 2.0, s = "three")
module.func(s = "three", d = 2.0, i = 1)
您可能会想出更多变化。
PyArg_ParseTuple函数
下面是 PyArg_ParseTuple 函数的标准签名:
int PyArg_ParseTuple(PyObject* tuple,char* format,...)
此函数返回错误时为0,成功时不为0。tuple是C函数的第二个参数的PyObject*。在这里, _format_ 是描述强制和可选参数的C字符串。
下面是 PyArg_ParseTuple 函数的格式代码列表:
代码 | C类型 | 含义 |
---|---|---|
c |
char | Python的长度为1的字符串变为C char类型。 |
d |
double | Python的浮点数变为C double类型。 |
f |
float | Python的浮点数变为C float类型。 |
i |
int | Python的整数变为C int类型。 |
l |
long | Python的整数变为C long类型。 |
L |
long long | Python的整数变为C long long类型。 |
O |
PyObject* |
获取Python参数的非NULL借用引用。 |
s |
char* |
Python的不含空字符的字符串变为C char*类型。 |
s# |
char*+int |
将任意Python字符串变为C地址和长度。 |
t# |
char*+int |
将只读单段缓冲区变为C地址和长度。 |
u |
Py_UNICODE* |
Python Unicode字符串不含空字符变为C。 |
u# |
Py_UNICODE*+int |
将任意Python Unicode字符串变为C地址和长度。 |
w# |
char*+int |
将读写单段缓冲区变为C地址和长度。 |
z |
char* |
类似于s,也接受None(将C char*设为NULL)。 |
z# |
char*+int |
类似于s#,也接受None(将C char*设为NULL)。 |
(...) |
根据… | 将Python序列视为每个项目一个参数。 |
| |
下列参数为可选。 | |
: |
格式化结束,后跟函数名称以供错误消息使用。 | |
; |
格式化结束,后跟整个错误消息文本。 |
返回值
Py_BuildValue 类似于 PyArg_ParseTuple ,需要一个格式字符串。不同的是,它不需要输入要构建的值的地址,而是直接输入实际的值。这里有一个示例,展示了如何实现一个加法函数 −
static PyObject *foo_add(PyObject *self, PyObject *args) {
int a;
int b;
if (!PyArg_ParseTuple(args, "ii", &a, &b)) {
return NULL;
}
return Py_BuildValue("i", a + b);
}
如果用Python实现,代码如下 −
def add(a, b):
return (a + b)
您可以按如下方式从函数返回两个值。在Python中,使用列表将其捕获。
static PyObject *foo_add_subtract(PyObject *self, PyObject *args) {
int a;
int b;
if (!PyArg_ParseTuple(args, "ii", &a, &b)) {
return NULL;
}
return Py_BuildValue("ii", a + b, a - b);
}
如果用Python实现,代码如下 −
def add_subtract(a, b):
return (a + b, a - b)
Py_BuildValue 函数
这是 Py_BuildValue 函数的标准签名 −
PyObject* Py_BuildValue(char* format,...)
这里的 format 是描述要构建的Python对象的C字符串。以下是要从中构建结果的C值的 Py_BuildValue 参数。结果是一个新的 PyObject* 引用。
以下表格列出了通常使用的代码字符串,其中零个或多个被连接成字符串格式。
Code | C type | Meaning |
---|---|---|
c |
char | 一个C char变成Python字符串,长度为1. |
d |
double | 一个C double变成Python float. |
f |
float | 一个C float变成Python float. |
i |
int | 一个C int变成Python int. |
l |
long | 一个C long变成Python int. |
N |
PyObject* |
传递Python对象并获得引用。 |
O |
PyObject* |
传递Python对象并以正常方式增加引用。 |
O& |
转换+void* |
任意转换 |
s |
char* |
C 0终止的char * 转换成Python字符串,或空转换为None。 |
s# |
char*+int |
C char* 和长度转换成Python字符串,或空转换为None。 |
u |
Py_UNICODE* |
C宽字符,以null结尾的字符串转换为Python Unicode,或空转换为None。 |
u# |
Py_UNICODE*+int |
C宽字符串和长度转换为Python Unicode,或空转换为None。 |
w# |
char*+int |
从C地址和长度读/写单个段缓冲区。 |
z |
char* |
像s一样,也接受None(将C char * 设置为NULL)。 |
z# |
char*+int |
像s#一样,也接受None(将C char * 设置为NULL)。 |
(...) |
象…… | 从C值构建Python元组。 |
[...] |
象…… | 从C值构建Python列表。 |
{...} |
象…… | 从C值构建Python字典,交替键和值。 |
Code {…} 从偶数个C值交替生成键和值的Python字典。例如,Py_BuildValue(“{issi}”,23,”zig”,”zag”,42)返回类似Python的{23:’zig’,’zag’:42}的字典。