Python 更多扩展
您使用任何编译语言,如C、C++或Java编写的代码都可以集成或导入到另一个Python脚本中。这段代码被认为是一个“扩展”。
Python扩展模块实际上只是一个普通的C库。在Unix机器上,这些库通常以 .so 结尾(表示共享对象)。在Windows机器上,通常以 .dll 结尾(表示动态链接库)。
编写扩展的先决条件
要开始编写扩展,您将需要Python头文件。
- 在Unix机器上,通常需要安装一个特定于开发人员的软件包。
-
当Windows用户使用二进制Python安装程序时,这些头文件作为软件包的一部分提供。
此外,假设您具有良好的C或C++知识,以便使用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中没有void函数,就像C语言中那样。如果你不想让你的函数返回一个值,就返回Python中的C等效值 None 。Python头文件定义了一个宏,Py_RETURN_NONE,它可以为我们完成这个工作。
你的C函数的名称可以任意,因为它们从未在扩展模块之外被看到。它们被定义为静态函数。
你的C函数通常是通过将Python模块和函数名称组合在一起来命名的,如下所示−
static PyObject *module_func(PyObject *self, PyObject *args) {
/* Do your stuff here. */
Py_RETURN_NONE;
}
这是一个名为func的Python函数,位于模块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解释器会调用此函数。它要求函数的名称为 initModule ,其中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的系统上,为了拥有写入site-packages目录的权限,您很可能需要作为root用户运行该命令。在Windows上通常不会出现此问题。
导入扩展
安装扩展后,您可以按照以下方式导入并调用Python脚本中的扩展:
import helloworld
print helloworld.helloworld()
这会产生如下输出: output -
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参数。这是您将要解析的对象。第二个参数是一个格式字符串,描述了您希望参数出现的方式。每个参数在格式字符串中由一个或多个字符表示,如下所示。
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函数
re是 PyArg_ParseTuple 函数的标准签名−
int PyArg_ParseTuple(PyObject* tuple,char* format,...)
这个函数在出现错误时返回0,成功时返回一个不等于0的值。Tuple是C函数的第二个参数的PyObject*。在这里,format是一个描述必需和可选参数的C字符串。
下面是 PyArg_ParseTuple 函数的格式代码列表−
代码 | C 类型 | 意义 |
---|---|---|
c | char | Python 的长度为1的字符串变成 C 的字符。 |
d | double | Python 的浮点数变成 C 的双精度浮点数。 |
f | float | Python 的浮点数变成 C 的单精度浮点数。 |
i | int | Python 的整数变成 C 的整数。 |
l | long | Python 的整数变成 C 的长整数。 |
L | long long | Python 的整数变成 C 的长长整数。 |
O | PyObject* | 获取非NULL引用到Python参数。 |
S | char* | 将Python字符串转换为C的char*,不包含嵌入的null字符。 |
s# | char*+int | 将任何Python字符串转换为C地址和长度。 |
t# | char*+int | 将只读的单段缓冲区转换为C地址和长度。 |
u | Py_UNICODE* | 将Python Unicode字符串转换为C,不包含嵌入的null字符。 |
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字符串。 Py_BuildValue的以下参数都是从C值中构建结果。 PyObject* result
是一个新的引用。
下表列出了常用的代码字符串,其中零个或多个串联成一个字符串格式。
代码 | C类型 | 意义 |
---|---|---|
c | char | C的char类型变为长度为1的Python字符串。 |
d | double | C的double类型变为Python的浮点数。 |
f | float | C的float类型变为Python的浮点数。 |
i | int | C的int类型变为Python的整数。 |
l | long | C的long类型变为Python的整数。 |
N | PyObject* |
传递Python对象并窃取其引用。 |
O | PyObject* |
传递Python对象,并像正常情况下增加其引用计数。 |
O& |
convert+void* | 任意类型的转换 |
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转为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值中构建字典,交替为键和值。例如,Py_BuildValue(“{issi}”,23,”zig”,”zag”,42) 返回一个类似Python的字典 {23:’zig’,’zag’:42}