亿迅智能制造网
工业4.0先进制造技术信息网站!
首页 | 制造技术 | 制造设备 | 工业物联网 | 工业材料 | 设备保养维修 | 工业编程 |
home  MfgRobots >> 亿迅智能制造网 >  >> Industrial programming >> Python

Python C 扩展开发指南:构建高性能原生模块

使用 C、C++ 或 Java 等编译语言编写的代码,可集成或导入到 Python 脚本中,称为“扩展”。

Python 扩展模块本质是普通 C 库。Unix 下通常为 .so(共享对象),Windows 下为 .dll(动态链接库)。

编写扩展的前置条件

需要 Python 头文件:

假设你具备良好 C/C++ 基础。

Python 扩展初探

扩展模块代码分四部分:

  1. 头文件 Python.h。
  2. 暴露给 Python 的 C 函数。
  3. 方法映射表:将 Python 可见函数名映射到 C 函数。
  4. 初始化函数。

头文件 Python.h

在 C 源文件包含 Python.h,获取内部 Python API 挂载模块到解释器。必须在其他头文件前包含。

C 函数签名

C 实现函数签名三种形式之一:

static PyObject *MyFunction(PyObject *self, PyObject *args);
static PyObject *MyFunctionWithKeywords(PyObject *self, PyObject *args, PyObject *kw);
static PyObject *MyFunctionWithNoArgs(PyObject *self);

均返回 Python 对象。无 void 函数,不想返回值用 Py_RETURN_NONE 宏返回 None。C 函数名可任意,定义为 static。习惯命名为 module_func 形式。

static PyObject *module_func(PyObject *self, PyObject *args) {
    /* Do your stuff here. */
    Py_RETURN_NONE;
}

方法映射表

方法表是 PyMethodDef 结构体数组:

struct PyMethodDef {
    char *ml_name;
    PyCFunction ml_meth;
    int ml_flags;
    char *ml_doc;
};

成员说明:

表以 {NULL, NULL, 0, NULL} 哨兵结尾。

示例

static PyMethodDef module_methods[] = {
    {"func", (PyCFunction)module_func, METH_NOARGS, NULL},
    {NULL, NULL, 0, NULL}
};

初始化函数

模块加载时由解释器调用,函数名必须为 initModule(Module 为模块名)。需从库导出,使用 PyMODINIT_FUNC 宏。结构:

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...");
}

完整实例:helloworld

#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,下面演示编译安装。

构建与安装扩展

distutils 包简化模块分发。通过 setup.py 脚本构建安装:

from distutils.core import setup, Extension
setup(name='helloworld', version='1.0',
      ext_modules=[Extension('helloworld', ['hello.c'])]) 

执行命令完成编译链接并复制动态库到 site-packages:

$ python setup.py install

Unix 需 root 权限写入 site-packages,Windows 通常无需。

导入扩展

安装后即可在 Python 脚本导入调用:

#!/usr/bin/python
import helloworld
print(helloworld.helloworld())

输出:

Hello, Python extensions!!

传递函数参数

定义接受参数的函数,使用带 args 的签名:

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}
};

使用 PyArg_ParseTuple 从 args 提取参数。第一参数为 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 函数详解

标准签名:

int PyArg_ParseTuple(PyObject* tuple, char* format, ...)

返回 0 表示错误,非 0 成功。tuple 为 C 函数第二参数。format 为 C 字符串描述必选/可选参数。格式代码表:

代码C 类型含义
cchar长度 1 的 Python 字符串转 C char
ddoublePython float 转 C double
ffloatPython float 转 C float
iintPython int 转 C int
llongPython int 转 C long
Llong longPython int 转 C long long
OPyObject*获取非空借用引用
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
(...)按 ... 解析Python 序列视为每项一个参数
|后续参数为可选
:格式结束,后跟函数名用于错误信息
;格式结束,后跟完整错误信息文本

返回值

Py_BuildValue 接受类似 PyArg_ParseTuple 的格式字符串,但传入实际值而非地址。实现加法示例:

static PyObject *foo_add(PyObject *self, PyObject *args) {
    int a, 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, 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 函数详解

标准签名:

PyObject* Py_BuildValue(char* format, ...)

format 描述要构建的 Python 对象,后续参数为 C 值。返回新引用 PyObject*。常用格式代码:

代码C 类型含义
ccharC char 转长度 1 Python 字符串
ddoubleC double 转 Python float
ffloatC float 转 Python float
iintC int 转 Python int
llongC long 转 Python int
NPyObject*传递 Python 对象并窃取引用
OPyObject*传递 Python 对象并正常 INCREF
O&convert+void*任意转换
schar*C 以空结尾字符串转 Python 字符串,NULL 转 None
s#char*+intC 字符串和长度转 Python 字符串,NULL 转 None
uPy_UNICODE*C 宽字符串转 Python Unicode,NULL 转 None
u#Py_UNICODE*+intC 宽字符串和长度转 Python Unicode,NULL 转 None
w#char*+int读写单段缓冲区转 C 地址和长度
zchar*同 s,接受 None
z#char*+int同 s#,接受 None
(...)按 ... 解析从 C 值构建 Python 元组
[...]按 ... 解析从 C 值构建 Python 列表
{...}按 ... 解析从 C 值构建 Python 字典,键值交替

{...} 从偶数个 C 值构建字典,键值交替。例如 Py_BuildValue("{issi}",23,"zig","zag",42) 返回类似 Python {23:'zig','zag':42} 的字典。

Python

  1. 精通 Python 面向对象编程:类、继承、封装与多态
  2. Python print() 函数完全指南:基础用法、空行打印与自定义结束符
  3. Python strip() 方法完全指南:去除首尾空白与自定义字符实战
  4. Python str.count() 完全指南:统计字符与子串出现次数实战
  5. Python format() 字符串格式化完全指南:占位符、格式化规范、类与字典应用
  6. Python 字符串 find() 方法完全指南:语法、参数、替代方案与计数技巧
  7. 精通 Python Lambda 函数:实用示例与最佳实践
  8. Python round() 函数详解与实战示例
  9. 精通 Python map() 函数:语法、示例与最佳实践
  10. Python timeit():精准测量代码执行时间与实战示例
  11. Python collections.Counter:高效计数、更新与算术运算
  12. Python list.count() 专家指南:语法、示例与性能解析