📜  Python使用C进行扩展编程

📅  最后修改于: 2020-12-23 05:30:15             🧑  作者: Mango


您使用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

您需要在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函数。如果您不希望函数返回值,请返回C的Python 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函数。您将把指向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 ,其中Module模块的名称。

需要从将要构建的库中导出初始化函数。 Python标头定义了PyMODINIT_FUNC,以包括针对我们正在编译的特定环境而发生的适当提示。您需要做的就是在定义函数时使用它。

您的C初始化函数通常具有以下总体结构-

PyMODINIT_FUNC initModule() {
   Py_InitModule3(func, module_methods, "docstring...");
}

这是Py_InitModule3函数的描述-

  • func-这是要导出的函数。

  • module _methods-这是上面定义的映射表名称。

  • docstring-这是您要在扩展名中给出的注释。

将所有内容放在一起如下所示-

#include 

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 

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/python
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参数。这是您将解析的对象。第二个参数是一个格式字符串,描述了您希望它们出现的参数。每个自变量由格式字符串中的一个或多个字符表示,如下所示。

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 A Python string of length 1 becomes a C char.
d double A Python float becomes a C double.
f float A Python float becomes a C float.
i int A Python int becomes a C int.
l long A Python int becomes a C long.
L long long A Python int becomes a C long long
O PyObject* Gets non-NULL borrowed reference to Python argument.
s char* Python string without embedded nulls to C char*.
s# char*+int Any Python string to C address and length.
t# char*+int Read-only single-segment buffer to C address and length.
u Py_UNICODE* Python Unicode without embedded nulls to C.
u# Py_UNICODE*+int Any Python Unicode C address and length.
w# char*+int Read/write single-segment buffer to C address and length.
z char* Like s, also accepts None (sets C char* to NULL).
z# char*+int Like s#, also accepts None (sets C char* to NULL).
(…) as per … A Python sequence is treated as one argument per item.
|   The following arguments are optional.
:   Format end, followed by function name for error messages.
;   Format end, followed by entire error message text.

返回值

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是一个C字符串,描述了要构建的Python对象。 Py_BuildValue的以下参数是从中生成结果的C值。 PyObject *结果是一个新引用。

下表列出了常用的代码字符串,其中零个或多个以字符串格式连接。

Code C type Meaning
c char A C char becomes a Python string of length 1.
d double A C double becomes a Python float.
f float A C float becomes a Python float.
i int A C int becomes a Python int.
l long A C long becomes a Python int.
N PyObject* Passes a Python object and steals a reference.
O PyObject* Passes a Python object and INCREFs it as normal.
O& convert+void* Arbitrary conversion
s char* C 0-terminated char* to Python string, or NULL to None.
s# char*+int C char* and length to Python string, or NULL to None.
u Py_UNICODE* C-wide, null-terminated string to Python Unicode, or NULL to None.
u# Py_UNICODE*+int C-wide string and length to Python Unicode, or NULL to None.
w# char*+int Read/write single-segment buffer to C address and length.
z char* Like s, also accepts None (sets C char* to NULL).
z# char*+int Like s#, also accepts None (sets C char* to NULL).
(…) as per … Builds Python tuple from C values.
[…] as per … Builds Python list from C values.
{…} as per … Builds Python dictionary from C values, alternating keys and values.

代码{…}从偶数个C值,交替的键和值构建字典。例如,Py_BuildValue(“ {issi}”,23,“ zig”,“ zag”,42)返回类似于Python的字典{23:’zig’,’zag’:42}。