当前位置: 首页 > 文档资料 > Python 中文教程 >

Further Extensions

优质
小牛编辑
131浏览
2023-12-01

使用任何编译语言(如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函数的名称可以是您喜欢的任何名称,因为它们在扩展模块之外从未见过。 它们被定义为static函数。

您的C函数通常通过将Python模块和函数名称组合在一起来命名,如下所示 -

static PyObject *<i>module_func</i>(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对此标志进行按位OR运算。

    • 这也可以具有METH_NOARGS值,表示您不想接受任何参数。

  • ml_doc - 这是函数的docstring,如果你不想写一个函数,它可能是NULL。

此表需要使用由适当成员的NULL和0值组成的标记终止。

例子 (Example)

对于上面定义的函数,我们有以下方法映射表 -

static PyMethodDef <i>module</i>_methods[] = {
   { "<i>func</i>", (PyCFunction)<i>module_func</i>, METH_NOARGS, NULL },
   { NULL, NULL, 0, NULL }
};

初始化函数

扩展模块的最后一部分是初始化函数。 加载模块时,Python解释器会调用此函数。 要求该函数名为init Module ,其中ModuleModule的名称。

需要从要构建的库中导出初始化函数。 Python标头定义PyMODINIT_FUNC以包含适用于我们正在编译的特定环境的咒语。 您所要做的就是在定义函数时使用它。

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

PyMODINIT_FUNC init<i>Module</i>() {
   Py_InitModule3(<i>func</i>, <i>module</i>_methods, "docstring...");
}

这是Py_InitModule3函数的描述 -

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

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

  • docstring - 这是您要在扩展程序中提供的评论。

把这一切放在一起看起来如下 -

#include <Python.h>
static PyObject *<i>module_func</i>(PyObject *self, PyObject *args) {
   /* Do your stuff here. */
   Py_RETURN_NONE;
}
static PyMethodDef <i>module</i>_methods[] = {
   { "<i>func</i>", (PyCFunction)<i>module_func</i>, METH_NOARGS, NULL },
   { NULL, NULL, 0, NULL }
};
PyMODINIT_FUNC init<i>Module</i>() {
   Py_InitModule3(<i>func</i>, <i>module</i>_methods, "docstring...");
}

例子 (Example)

一个利用上述所有概念的简单例子 -

#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/python
import helloworld
print helloworld.helloworld()

这会产生以下结果 -

Hello, Python extensions!!

传递函数参数

由于您很可能希望定义接受参数的函数,因此可以使用C函数的其他签名之一。 例如,接受一些参数的跟随函数将被定义为这样 -

static PyObject *<i>module_func</i>(PyObject *self, PyObject *args) {
   /* Parse args and do something interesting here. */
   Py_RETURN_NONE;
}

包含新函数条目的方法表如下所示 -

static PyMethodDef <i>module</i>_methods[] = {
   { "<i>func</i>", (PyCFunction)<i>module_func</i>, METH_NOARGS, NULL },
   { "<i>func</i>", <i>module_func</i>, METH_VARARGS, NULL },
   { NULL, NULL, 0, NULL }
};

您可以使用API PyArg_ParseTuple函数从传递给C函数的一个PyObject指针中提取参数。

PyArg_ParseTuple的第一个参数是args参数。 这是您要parsing的对象。 第二个参数是一个格式字符串,用于描述您希望它们出现的参数。 每个参数由格式字符串中的一个或多个字符表示,如下所示。

static PyObject *<i>module_func</i>(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函数的格式代码列表 -

C型含义
cchar长度为1的Python字符串变为C字符。
ddoublePython float变为C double。
ffloatPython float变为C float。
iintPython int成为C int。
llongPython int变为C long。
Llong longPython int变为C long long
OPyObject*获取对Python参数的非NULL借用引用。
schar*Python字符串没有嵌入空值到C char *。
s#char*+int任何Python字符串到C地址和长度。
t#char*+int只读单段缓冲区到C地址和长度。
uPy_UNICODE*没有嵌入空值的Python Unicode到C语言
u#Py_UNICODE*+int任何Python Unicode C地址和长度。
w#char*+int读/写单段缓冲区到C地址和长度。
zchar*与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是一个C字符串,描述了要构建的Python对象。 Py_BuildValue的以下参数是Py_BuildValue构建结果的C值。 PyObject*结果是一个新的引用。

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

C型含义
ccharAC char成为长度为1的Python字符串。
ddoubleAC double成为Python float。
ffloatAC float成为Python float。
iintAC int成为Python int。
llongAC long成为Python int。
NPyObject*传递Python对象并窃取引用。
OPyObject*传递一个Python对象并正常INCREF它。
O&convert+void*Arbitrary conversion
schar*C 0终止char *到Python字符串,或NULL到None。
s#char*+intC char *和Python字符串的长度,或NULL到None。
uPy_UNICODE*C宽,以null结尾的字符串到Python Unicode,或NULL到None。
u#Py_UNICODE*+intC-wide字符串和长度为Python Unicode,或NULL为None。
w#char*+int读/写单段缓冲区到C地址和长度。
zchar*与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}。