使用C进行Python3扩展编程
你使用任何编译语言(例如C,C ++或Java)编写的任何代码都可以集成或导入另一个Python脚本中。该代码被视为“扩展名”。
Python扩展模块只不过是一个普通的C库。在Unix机器上,这些库通常以 .so (用于共享对象)。在Windows计算机上,你通常会看到 .dll (用于动态链接的库)。
编写扩展的学习前提
要开始编写扩展程序,你将需要Python头文件。
-
在Unix机器上,这通常需要安装开发人员专用的软件包,例如 python2.5-dev .
-
Windows用户在使用二进制Python安装程序时将这些标头作为软件包的一部分获得。
此外,假定你具有C或C ++的丰富知识,可以使用C编程编写任何Python扩展。
首先看一下Python扩展
首先看一下Python扩展模块,你需要将代码分为四个部分:
-
头文件 Python.h .
-
你想作为模块接口公开的C函数。
-
一张表,映射了你作为Python开发人员的函数名称,将其视为扩展模块中的C函数。
-
初始化函数。
头文件Python.h
你需要包括 Python.h C源文件中的头文件,该文件可让你访问用于将模块挂接到解释器的内部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对象。没有这样的事情 void Python中的函数与C中的函数一样。如果你不希望函数返回值,请返回Python的C等效项。 None 价值。 Python标头定义了一个宏Py_RETURN_NONE,它可以为我们执行此操作。
C函数的名称可以是你喜欢的任何名称,因为它们在扩展模块之外从未见过。它们被定义为 static 功能。
你的C函数通常是通过将Python模块和函数名称组合在一起来命名的,如下所示:
static PyObject *module_func(PyObject *self, PyObject *args) { /* Do your stuff here. */ Py_RETURN_NONE; }
这是一个Python函数,称为 func 在模块内部 module 。你将把指向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。
-
如果要允许关键字参数进入函数,可以将该标志与METH_KEYWORDS进行按位或运算。
-
它也可以具有METH_NOARGS的值,指示你不想接受任何参数。
-
-
ml_doc :这是该函数的文档字符串,如果你不想编写一个字符串,则可以为NULL。
该表需要以一个由NULL和0值组成的适当成员的标记终止。
对于上面定义的功能,我们有以下方法映射表:
static PyMethodDef module_methods[] = { { "func", (PyCFunction)module_func, METH_NOARGS, NULL }, { NULL, NULL, 0, NULL } };
初始化功能
扩展模块的最后一部分是初始化功能。加载模块时,Python解释器将调用此函数。要求函数被命名 init Module , where Module 是模块的名称。
需要从将要构建的库中导出初始化函数。 Python标头定义了PyMODINIT_FUNC,以包含针对我们正在编译的特定环境而发生的适当提示。你需要做的就是在定义函数时使用它。
你的C初始化函数通常具有以下总体结构:
PyMODINIT_FUNC initModule() { Py_InitModule3(func, module_methods, "docstring..."); }
这是对 Py_InitModule3 功能:
-
func :这是要导出的功能。
-
module_methods :这是上面定义的映射表名称。
-
文档字符串 :这是你想在扩展名中给出的注释。
将所有这些放在一起,如下所示:
#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!"); }
Here the Py_BuildValue 函数用于构建Python值。将以上代码保存在hello.c文件中。我们将看到如何编译和安装要从Python脚本调用的模块。
构建和安装扩展
The 发行版 软件包使以标准方式分发纯Python和扩展模块的Python模块变得非常容易。模块以源代码形式分发,通过通常称为的安装脚本来构建和安装 setup.py as.
对于以上模块,你需要准备以下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) { /* Parse args and do something interesting here. */ 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参数。这就是你将要成为的对象 parsing 。第二个参数是一个格式字符串,描述了你希望它们出现的参数。每个自变量由格式字符串中的一个或多个字符表示,如下所示。
static PyObject *module_func(PyObject *self, PyObject *args) { int i; double d; char *s; if (!PyArg_ParseTuple(args, "ids", &i, &d, &s)) { return NULL; } /* Do something interesting here. */ 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的值表示成功。元组是PyObject *,它是C函数的第二个参数。这里 format 是描述强制和可选参数的C字符串。
这是列表的格式代码 PyArg_ParseTuple 功能:
Code | C type | Meaning |
---|---|---|
c | char | 长度为1的Python字符串成为C字符。 |
d | double | Python浮点数变成C的双精度数。 |
f | float | Python浮点数变为C浮点数。 |
i | int | Python int变成C int。 |
l | long | Python int变成C长。 |
L | long long | Python int变成C long long |
O | PyObject * | 获取对Python参数的非NULL借用引用。 |
s | char* | 不带C null *的null的Python字符串。 |
s# | char * + int | 任何Python字符串到C地址和长度。 |
t# | char * + int | 只读单段缓冲区的C地址和长度。 |
u | Py_UNICODE * | 不带C的null的Python Unicode。 |
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)
The Py_BuildValue Function
这是用于的标准签名 Py_BuildValue 功能:
PyObject* Py_BuildValue(char* format,...)
Here format 是一个C字符串,描述了要构建的Python对象。以下论点 Py_BuildValue 是从中构建结果的C值。这 PyObject * 结果是一个新的参考。
下表列出了常用的代码字符串,其中零个或多个以字符串格式连接。
Code | C type | Meaning |
---|---|---|
c | char | C字符变为长度为1的Python字符串。 |
d | double | C double成为Python浮点数。 |
f | float | C浮点数成为Python浮点数。 |
i | int | C int成为Python int。 |
l | long | C long成为Python int。 |
N | PyObject * | 传递Python对象并窃取引用。 |
O | PyObject * | 传递一个Python对象,并像往常一样INCREFs。 |
O& | 转换+无效* | 任意转换 |
s | char* | C 0终止的char *到Python字符串,或者NULL到None。 |
s# | char * + int | C char *,长度为Python字符串,或NULL为None。 |
u | Py_UNICODE * | C范围的,以Null终止的字符串,转换为Python Unicode,或NULL,转换为None。 |
u# | Py_UNICODE * + int | C范围的字符串和长度为Python Unicode,或NULL为无。 |
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字典。 |
代码{...}从偶数个C值,交替的键和值构建字典。例如,Py_BuildValue(“ {issi}”,23,“ zig”,“ zag”,42)返回类似于Python的字典{23:'zig','zag':42}。