安爸-超级家庭

Python C扩展模块实例:example

安爸 发布于

在C中编写Python扩展模块的流程涉及多个核心步骤:

  • 包含Python头文件
  • 编写C函数逻辑
  • 定义模块方法表
  • 创建模块定义结构
  • 实现模块初始化函数

一.文件名examplemodule.c

`#include

// 1. 实现C函数(Python可调用的函数)
static PyObject example_add(PyObject self, PyObject *args){
int num1, num2;
// 解析两个整数参数
if (!PyArg_ParseTuple(args, "ii", &num1, &num2)) {
returnNULL; // 解析失败时返回异常
    }
return PyLong_FromLong(num1 + num2); // 返回Python整数对象
}

// 2. 定义模块方法表
static PyMethodDef ExampleMethods[] = {
    {"add", example_add, METH_VARARGS, "Add two integers."},
    {NULL, NULL, 0, NULL} // 哨兵值,标记结束
};

// 3. 定义模块结构
staticstructPyModuleDefexamplemodule = {
    PyModuleDef_HEAD_INIT,
"example",   // 模块名
"Example module documentation.",
-1,          // 全局状态(-1表示不使用GIL)
    ExampleMethods
};

// 4. 模块初始化函数(必须命名为PyInit_<模块名>)
PyMODINIT_FUNC PyInit_example(void){
return PyModule_Create(&examplemodule);
}
`

1.example_add()函数

这段代码实现了一个 Python C 扩展模块中的 add 函数。其功能如下:

static PyObject *example_add(PyObject *self, PyObject *args){ int num1, num2; if (!PyArg_ParseTuple(args, "ii", &num1, &num2)) { returnNULL;     } return PyLong_FromLong(num1 + num2); }

  • example_add 是一个 C 函数,供 Python 调用。
  • 参数 self 是模块自身(未使用),args 是传入的参数元组。
  • 使用 PyArg_ParseTuple(args, "ii", &num1, &num2) 解析传入的两个整数参数,分别赋值给 num1num2
  • 如果解析失败,返回 NULL,表示出错。
  • 如果成功,返回 num1 + num2 的和,类型为 Python 的长整型对象(PyLong_FromLong)。

该函数实现了 Python 层的 add(a, b),返回两个整数的和。

2.Python C扩展模块的方法表

这段代码定义了一个 Python C 扩展模块的方法表。具体说明如下:

// 模块方法表 static PyMethodDef ExampleMethods[] = {         {"add", example_add, METH_VARARGS, "Add two integers."},         {NULL, NULL, 0, NULL} };

  • static PyMethodDef ExampleMethods[]:声明一个方法表数组,用于注册模块中的所有方法。
  • 每个元素是一个结构体,包含四个字段:
    • 方法名(如 "add"),即 Python 中调用的函数名。
    • C 函数指针(如 example_add),实际实现该方法的 C 函数。
    • 调用方式(如 METH_VARARGS),表示参数以元组形式传递。
    • 方法的文档字符串(如 "Add two integers.")。
  • 最后一个元素必须全为 NULL0,表示方法表结束。

3.PyModuleDef结构体

examplemodule 是一个 PyModuleDef 结构体,用于定义 Python 扩展模块的元数据。

// 模块定义 staticstructPyModuleDefexamplemodule = {         PyModuleDef_HEAD_INIT, "example", "Example module documentation.", -1,         ExampleMethods };

各字段含义如下:

  • PyModuleDef_HEAD_INIT:结构体初始化宏,必须写。
  • "example":模块名称。
  • "Example module documentation.":模块文档字符串。
  • -1:模块的状态大小,-1 表示全局状态(不需要为每个解释器单独分配内存)。
  • ExampleMethods:模块中定义的方法表。

该结构体用于 PyModule_Create,从而生成 Python 可用的扩展模块对象。

4.Python C扩展模块的初始化函数

这段代码是 Python C 扩展模块的初始化函数,即这段代码实现了Python3扩展模块的标准初始化入口。

// 模块初始化函数 PyMODINIT_FUNC PyInit_example(void){ return PyModule_Create(&examplemodule); }

  • PyMODINIT_FUNC PyInit_example(void) 是模块初始化函数,名称必须为 PyInit_模块名,用于 Python 3 动态加载模块时调用。
  • PyModule_Create(&examplemodule) 创建并返回一个新的 Python 模块对象,examplemodule 是模块的定义结构体。
  • 该函数返回模块对象指针,供 Python 解释器加载模块时使用。

二.编译与安装流程

1. 创建setup.py

`from setuptools import setup, Extension

module = Extension(
'example',  # 模块名
    sources=['examplemodule.c'],  # C源文件
)

setup(
    name='example',
    version='1.0',
    description='Example C extension',
    ext_modules=[module],
)
`

2. 编译并安装

`# 安装到当前Python环境
python setup.py install

或直接编译(生成.so/.pyd文件)

python setup.py build_ext --inplace
`

如果执行python setup.py install,那么安装到d:\python310\lib\site-packages,如下所示:

如果执行python setup.py build_ext --inplace,那么生成example.cp310-win_amd64.pyd

3.在Python中使用

import example print(example.add(3, 7))  # 输出: 10

三.关键概念详解

1.函数签名

  • static PyObject* func(PyObject *self, PyObject *args)
  • args:包含所有参数的元组,需用PyArg_ParseTuple解析

2.参数解析

  • PyArg_ParseTuple(args, "ii", &a, &b):解析两个整数
  • 格式字符串:"i"(int), "f"(float), "s"(string)等

3.返回值处理

  • 返回Python对象:PyLong_FromLong(), PyFloat_FromDouble()
  • 错误时返回NULL并设置异常(如PyErr_SetString

4.引用计数

  • Python对象需手动管理引用
  • 使用Py_INCREF/Py_DECREF(简单函数中通常不需要)

四.添加main方法

要在 examplemodule.c 中添加 main 方法,使其既能作为 Python 扩展模块使用,又能作为独立可执行程序运行,可以通过条件编译实现。以下是完整代码:

`#include

include  // 添加标准输入输出头文件

// 实现C函数(Python可调用的函数)
static PyObject example_add(PyObject self, PyObject *args){
int num1, num2;
// 解析两个整数参数
if (!PyArg_ParseTuple(args, "ii", &num1, &num2)) {
returnNULL; // 解析失败时返回异常
    }
return PyLong_FromLong(num1 + num2);
}

// 定义模块方法表
static PyMethodDef ExampleMethods[] = {
    {"add", example_add, METH_VARARGS, "Add two integers."},
    {NULL, NULL, 0, NULL} // 结束标记
};

// 定义模块结构
staticstructPyModuleDefexamplemodule = {
    PyModuleDef_HEAD_INIT,
"example",   // 模块名
"Example module documentation.",
-1,          // 全局状态
    ExampleMethods
};

// 模块初始化函数
PyMODINIT_FUNC PyInit_example(void){
return PyModule_Create(&examplemodule);
}

/**** 使用Python C API的main函数 ****/
intmain(){
printf("Running as standalone program using Python C API\n");

// 初始化Python解释器
    Py_Initialize();

// 创建模块对象
    PyObject *pModule = PyModule_Create(&examplemodule);
if (!pModule) {
        PyErr_Print();
return1;
    }

// 将模块添加到sys.modules
    PyObject *sys_modules = PyImport_GetModuleDict();
    PyDict_SetItemString(sys_modules, "example", pModule);

// 调用模块中的add函数
    PyObject pFunc = PyObject_GetAttrString(pModule, "add");
if (pFunc && PyCallable_Check(pFunc)) {
// 准备参数 (5, 7)
        PyObject
pArgs = PyTuple_Pack(2, PyLong_FromLong(5), PyLong_FromLong(7));

// 调用函数
        PyObject *pValue = PyObject_CallObject(pFunc, pArgs);

if (pValue) {
// 获取并打印结果
long result = PyLong_AsLong(pValue);
printf("5 + 7 = %ld\n", result);

// 清理引用
            Py_DECREF(pValue);
        } else {
            PyErr_Print();
        }

// 清理引用
        Py_DECREF(pArgs);
        Py_DECREF(pFunc);
    } else {
        PyErr_Print();
    }

// 清理模块引用
    Py_DECREF(pModule);

// 关闭Python解释器
    Py_Finalize();

return0;
}
`

CMakeLists.txt文件,如下所示:

`cmake_minimum_required(VERSION 3.25)
project(CPythonExample C)

set(CMAKE_C_STANDARD 11)

include python headers files

include_directories(

       /usr/local/opt/python-3.14.0b1/include/python3.14

       /usr/local/opt/python-3.14.0b1/include/python3.14/internal

       /usr/local/opt/python-3.14.0b1/include/python3.14/cpython

)

Link python library

link_directories(/usr/local/opt/python-3.14.0b1/lib)

include python headers files

include_directories(
        /mnt/l/20200707_Python/PythonSource/cpython
        /mnt/l/20200707_Python/PythonSource/cpython/Include
        /mnt/l/20200707_Python/PythonSource/cpython/Include/internal
        /mnt/l/20200707_Python/PythonSource/cpython/Include/cpython
)

Link python library

link_directories(/mnt/l/20200707_Python/PythonSource/cpython)

add_executable(CPythonExample 0003-example/examplemodule.c
        Python/pythonrun.c
        Python/pylifecycle.c
        Python/ceval.c
        Python/pyarena.c
        0003-example/examplemodule.c)

Define Py_BUILD_CORE

target_compile_definitions(CPythonExample PRIVATE Py_BUILD_CORE)

link python library

target_link_libraries(CPythonExample python3.14 m)
`

1.作为独立程序编译运行

`# 编译
gcc examplemodule.c -o example

运行

./example
`

输出结果,如下所示:

Running as standalone program using Python C API 5 + 7 = 12

2. 作为Python扩展模块使用

`# 使用setup.py编译安装
python setup.py build_ext --inplace

在Python中使用

import example
print(example.add(5, 7))  # 输出 12
`

这种实现方式允许同一份代码文件:

  • 作为高性能 Python 扩展模块
  • 作为独立的 C 程序测试核心逻辑
  • 避免 Python 环境依赖影响核心算法测试
  • 方便进行单元测试和性能基准测试

五.相关技术点

1.Py_Initialize()

用于初始化 Python 解释器。调用此函数后,C 程序可以使用 Python C API 执行 Python 代码、创建对象等。必须在使用任何 Python API 之前调用,且在程序结束时应调用 Py_Finalize(); 关闭解释器。

2.PyModule_Create()

PyModule_Create 是 Python C API 中用于创建新的模块对象的函数。它的原型如下:

PyObject* PyModule_Create(PyModuleDef *module);

  • 参数 module 是一个指向 PyModuleDef 结构体的指针,该结构体定义了模块的名称、文档字符串、方法表等信息。
  • 调用 PyModule_Create 会根据 PyModuleDef 的内容创建并返回一个新的 Python 模块对象(PyObject*)。这个对象可以被添加到 Python 解释器的模块字典中,从而在 Python 代码中导入和使用。

3.PyImport_GetModuleDict()

// 将模块添加到sys.modules PyObject *sys_modules = PyImport_GetModuleDict(); PyDict_SetItemString(sys_modules, "example", pModule);

  • PyImport_GetModuleDict 是 Python C API 的一个函数,用于获取当前解释器的模块字典(等同于 Python 里的 sys.modules),这个字典保存了所有已加载模块的名称和模块对象的映射。
  • PyDict_SetItemString 是用于操作 Python 字典对象的 C API 函数。它的作用是将一个键值对插入到指定的 Python 字典中,键为 C 字符串,值为 PyObject*。在上述代码中,它用于把自定义模块对象添加到 sys.modules 字典,使得该模块可以被 Python 代码访问和导入。

4.PyObject_GetAttrString()

在该代码中,它用于从模块对象 pModule 获取名为 "add" 的函数对象:

PyObject *pFunc = PyObject_GetAttrString(pModule, "add");

PyObject_GetAttrString 是 Python C API 中的一个函数,用于获取指定 Python 对象的某个属性。原型:

PyObject *PyObject_GetAttrString(PyObject *o, constchar *attr_name);

参数说明:

  • o:目标 Python 对象(如模块、类、实例等)。
  • attr_name:属性名(C 字符串)。

返回值:

  • 成功时返回属性对应的 PyObject* 指针(需手动 Py_DECREF)。
  • 失败时返回 NULL,并设置异常。

5.PyCallable_Check(pFunc)

用于判断 pFunc 是否是一个可调用对象(如函数、方法、类等)。如果 pFunc 可以像函数一样被调用,则返回非零值(True),否则返回0(False)。这通常用于在调用对象前确保其可调用,避免运行时错误。

6.PyTuple_PackPyObject_CallObject

`// 准备参数 (5, 7)
PyObject *pArgs = PyTuple_Pack(2, PyLong_FromLong(5), PyLong_FromLong(7));

// 调用函数
PyObject *pValue = PyObject_CallObject(pFunc, pArgs);
`

  • PyTuple_Pack 用于创建一个新的 Python 元组对象,并将传入的 C 变量作为元组的元素。例如,PyTuple_Pack(2, PyLong_FromLong(5), PyLong_FromLong(7)) 会生成一个包含 5 和 7 的元组 (5, 7)
  • PyObject_CallObject 用于调用一个可调用的 Python 对象(如函数),第一个参数是要调用的对象,第二个参数是传递给该对象的参数(通常是元组)。例如,PyObject_CallObject(pFunc, pArgs) 会调用 pFunc,并将 pArgs 作为参数传递。

7.Py_DECREFPy_INCREF

Py_DECREFPy_INCREF 是 Python C API 中用于管理对象引用计数的宏。

  • Py_INCREF(obj):将 obj 的引用计数加 1,表示又有一个地方持有了该对象的引用。
  • Py_DECREF(obj):将 obj 的引用计数减 1,如果引用计数变为 0,则自动释放该对象占用的内存。

它们用于防止内存泄漏和悬挂指针,确保 Python 对象的生命周期被正确管理。

8.Py_Finalize()

Py_Finalize() 是 Python C API 中用于关闭 Python 解释器的函数。它会清理所有 Python 资源、释放内存、关闭打开的文件和模块,并使 Python 解释器进入未初始化状态。调用后,不能再使用任何 Python C API,除非重新调用 Py_Initialize()。通常在嵌入式 Python 程序结束前调用。

参考文献

[0] Python C扩展模块实例:example:https://z0yrmerhgi8.feishu.cn/wiki/CRdBwu8dsiAicakGjxTcR26fn6g

[1] 使用C或C++扩展Python:https://docs.python.org/zh-cn/3.13/extending/extending.html


知识星球服务内容:Dify源码剖析及答疑,Dify对话系统源码,NLP电子书籍报告下载,公众号所有付费资料。加微信buxingtianxia21进NLP工程化资料群

(文:NLP工程化)

Python C扩展模块实例:example最先出现在每时AI


扫描二维码,在手机上阅读