你用任何编译语言(如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语言实现签名总是取以下三种形式之一 −
python
static PyObject *MyFunction(PyObject *self, PyObject *args);
static PyObject *MyFunctionWithKeywords(PyObject *self,
PyObject *args,
PyObject *kw);
static PyObject *MyFunctionWithNoArgs(PyObject *self);
上述每个声明都返回一个 Python 对象。Python 中没有像 C 那样的空函数。如果你不想让函数返回某个值,可以返回 Python 的 None 值的 C 版本。Python 头部定义了一个宏,Py_RETURN_NONE,帮我们完成了这个功能。
你的C函数名称可以随你喜欢,因为它们从未出现在扩展模块之外。它们被定义为静态函数。
你的 C 函数通常通过将 Python 模块和函数名合并命名,如图所示 −
python
static PyObject *module_func(PyObject *self, PyObject *args) {
/* Do your stuff here. */
Py_RETURN_NONE;
}
这是模块模块内的一个叫 func 的 Python 函数。你会把指向 C 函数的指针放到通常源代码下一个模块的方法表里。
方法映射表
该方法表是一个简单的 PyMethodDef 结构数组。该结构大致如是 −
python
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的值,表明你不想接受任何论点。
-
-
mml_doc − 这是函数的docstring,如果不这样,它可能是NULL 想写一篇。
该表需要以一个由 NULL 和 0 值组成的哨兵来结束,适用于相应的成员。
示例
对于上述定义的函数,我们有以下方法映射表 −
python
static PyMethodDef module_methods[] = {
{ "func", (PyCFunction)module_func, METH_NOARGS, NULL },
{ NULL, NULL, 0, NULL }
};
初始化函数
扩展模块的最后一部分是初始化函数。当模块加载时,Python 解释器调用了这个函数。函数必须命名为 initModule,其中 Module 是模的名称。
初始化函数需要从你要构建的库导出。Python 头部定义了 PyMODINIT_FUNC,包含针对我们编译的特定环境所需的相应咒语。你只需要在定义函数时使用它。
你的C初始化函数通常具有以下总体结构------
python
PyMODINIT_FUNC initModule() {
Py_InitModule3(func, module_methods, "docstring...");
}
以下是Py_InitModule3函数−的描述
-
func − 这是要导出的函数。
-
module_methods − 这是上述定义的映射表名称。
-
docstring − 这是你想在扩展中给出的评论。
综合这些,看起来如下 −
python
#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...");
}
示例
一个利用上述所有概念的简单例子 −
python
#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_func示例
一个利用上述所有概念的简单例子 −
s,
"Extension module example!");
}
这里使用Py_BuildValue函数构建 Python 值。把上面的代码保存在hello.c文件里。我们会学习如何编译并安装这个模块,以便用 Python 脚本调用。
建设与安装扩展部分
distutils 包让以标准方式分发纯 Python 模块和扩展模块变得非常简单。模块以源代码形式分发,通过通常称为 setup.pyas 的设置脚本构建和安装。
对于上述模块,你需要准备以下 setup.py 脚本 −
python
from distutils.core import setup, Extension
setup(name='helloworld', version='1.0', \
ext_modules=[Extension('helloworld', ['hello.c'])])
现在,使用以下命令,该命令将执行所有必要的编译和链接步骤,配合正确的编译器和链接器命令及标志,并将生成的动态库复制到相应的目录 −
python
$ python setup.py install
在基于 Unix 的系统中,你很可能需要以 root 身份运行此命令,才能获得写入 site-packages 目录的权限。这通常在Windows上不是问题。
导入扩展
安装扩展后,你可以导入并调用Python脚本中的扩展,具体作如下 −
python
import helloworld
print helloworld.helloworld()
这会产生以下输出 −
python
Hello, Python extensions!!
传递函数参数
由于你很可能会想定义接受参数的函数,你可以用其他签名来做你的 C 函数。例如,以下接受一定参数的函数定义如下 −
python
static PyObject *module_func(PyObject *self, PyObject *args) {
/* Parse args and do something interesting here. */
Py_RETURN_NONE;
}
包含新函数条目的方法表将呈现如下 −
python
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 论证。这就是你要解析的对象。第二个参数是一个格式字符串,描述你预期的参数。每个参数由格式字符串中的一个或多个字符表示,具体如下。
python
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;
}
编译并导入模块的新版本后,你可以调用任意数量任意类型的参数 −
python
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函数 − 的标准签名
python
int PyArg_ParseTuple(PyObject* tuple,char* format,...)
该函数错误返回0,成功返回不等于0的值。元组是 C 函数的第二个参数 PyObject*。这里的格式是描述强制和可选参数的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非常相似的格式字符串。您传递的不是正在构建的值的地址,而是实际值。下面是一个示例,显示了如何实现add函数。
python
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 实现,这会是它的样子 −
python
def add(a, b):
return (a + b)
你可以从函数中返回两个值,具体如下。这会用Python中的列表来捕捉。
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 实现,这会是它的样子 −
python
def add_subtract(a, b):
return (a + b, a - b)
Py_BuildValue功能
这里是函数−的标准签名Py_BuildValue
python
PyObject* Py_BuildValue(char* format,...)
这里的格式是一个描述要构建的 Python 对象的 C 字符串。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 | 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}这样的词典