Python C扩展模块实例:example

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

  • 包含Python头文件

  • 编写C函数逻辑

  • 定义模块方法表

  • 创建模块定义结构

  • 实现模块初始化函数

一.文件名examplemodule.c

#include<Python.h>

// 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."},
    {NULLNULL0NULL// 哨兵值,标记结束
};

// 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) 解析传入的两个整数参数,分别赋值给 num1 和 num2

  • 如果解析失败,返回 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."},
        {NULLNULL0NULL}
};
  • static PyMethodDef ExampleMethods[]:声明一个方法表数组,用于注册模块中的所有方法。

  • 每个元素是一个结构体,包含四个字段:

    • 方法名(如 "add"),即 Python 中调用的函数名。

    • C 函数指针(如 example_add),实际实现该方法的 C 函数。

    • 调用方式(如 METH_VARARGS),表示参数以元组形式传递。

    • 方法的文档字符串(如 "Add two integers.")。

  • 最后一个元素必须全为 NULL 或 0,表示方法表结束。

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(37))  # 输出: 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<Python.h>
#include<stdio.h>  // 添加标准输入输出头文件

// 实现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."},
    {NULLNULL0NULL// 结束标记
};

// 定义模块结构
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_DECREF 和 Py_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工程化)

发表评论