Python 更多扩展

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}

Python教程

Java教程

Web教程

数据库教程

图形图像教程

大数据教程

开发工具教程

计算机教程