本文介绍了一个完整的Python C扩展模块实现,包含可执行的源码和详细解释。这个模块名为spam
,提供了一个system()
函数来执行系统命令,并自定义了异常处理。
这个示例展示了Python C扩展的基本结构和工作原理,包括参数解析、异常处理、模块初始化和系统调用集成等关键概念。文件spammodule.c,如下所示:
#define PY_SSIZE_T_CLEAN
#include<Python.h>
#include<stdlib.h> /* system()、exit() */
#include<stdio.h> /* printf(), fprintf() */
/* 全局异常对象指针 */
static PyObject *SpamError = NULL;
/*
* 模块级函数:spam.system(command)
* 接受一个C字符串参数,调用system(command),如果system()出错则抛出SpamError,否则返回其退出状态码(整数)
*/
static PyObject *
spam_system(PyObject *self, PyObject *args){
constchar *command;
int sts;
// 1.从Python参数中解析出一个字符串
if (!PyArg_ParseTuple(args, "s", &command))
returnNULL;
// 2.调用C标准库的system()
sts = system(command);
if (sts < 0) {
// 如果system()返回<0,认为执行失败,抛出自定义异常
PyErr_SetString(SpamError, "System command failed");
returnNULL;
}
// 3.将整型sts转换为Python整数(PyLong)并返回
return PyLong_FromLong(sts);
}
/*
* 模块执行时的初始化函数(Module Exec Slot)
* 在这里创建自定义异常,并把它添加到模块对象中(名字为SpamError)
*/
staticint
spam_module_exec(PyObject *m){
// 创建一个新的异常类spam.error,继承自Exception
SpamError = PyErr_NewException("spam.error", NULL, NULL);
if (SpamError == NULL)
return-1;
// 将异常对象添加到模块m中,名称为SpamError
if (PyModule_AddObjectRef(m, "SpamError", SpamError) < 0) {
Py_DECREF(SpamError); // 失败时减少引用计数
SpamError = NULL; // 防止悬空指针
return-1;
}
return0;
}
/* 模块清理函数,当模块被卸载时调用 */
staticvoidspam_module_free(void *m){
Py_CLEAR(SpamError); // 安全地减少引用计数并置为NULL
}
/* 模块方法表:将Python名称映射到C函数 */
static PyMethodDef spam_methods[] = {
{
"system", // Python端调用名
(PyCFunction) spam_system, // 对应的C函数指针
METH_VARARGS, // 接受位置参数 (tuple)
"Execute a shell command and return its exit status."// docstring
},
{NULL, NULL, 0, NULL} // 末尾哨兵
};
/* 定义Module Execution Slot:导入时执行spam_module_exec() */
static PyModuleDef_Slot spam_module_slots[] = {
{Py_mod_exec, spam_module_exec},
{0, NULL}
};
/* 定义模块对象结构 */
staticstructPyModuleDefspam_module = {
PyModuleDef_HEAD_INIT,
.m_name = "spam", /* 模块名,必须与下面 PyInit_spam 保持一致 */
.m_doc = "Example module that wraps system().",
.m_size = 0, /* 0 表示不为 sub-interpreters 分配独立状态 */
.m_methods = spam_methods, /* 方法表 */
.m_slots = spam_module_slots, /* 执行槽,用于初始化自定义异常 */
.m_free = spam_module_free // 添加清理函数
};
/* 模块初始化函数:Python调用此函数来创建模块对象 */
PyMODINIT_FUNC PyInit_spam(void){
return PyModuleDef_Init(&spam_module);
}
一.源码解释
1.头文件和全局变量
#define PY_SSIZE_T_CLEAN
#include<Python.h>
#include<stdlib.h> /* system()、exit() */
#include<stdio.h> /* printf(), fprintf() */
/* 全局异常对象指针 */
static PyObject *SpamError = NULL;
-
PY_SSIZE_T_CLEAN
:确保在API中使用Py_ssize_t
类型代替int
-
Python.h
:Python C API的核心头文件 -
stdlib.h
:提供system()
函数声明 -
stdio.h
:可以使用如printf()
和fprintf()
这样的标准输入输出函数 -
SpamError
:自定义异常类的全局指针
2.核心函数实现
/*
* 模块级函数:spam.system(command)
* 接受一个C字符串参数,调用system(command),如果system()出错则抛出SpamError,否则返回其退出状态码(整数)
*/
static PyObject *
spam_system(PyObject *self, PyObject *args){
constchar *command;
int sts;
// 1.从Python参数中解析出一个字符串
if (!PyArg_ParseTuple(args, "s", &command))
returnNULL;
// 2.调用C标准库的system()
sts = system(command);
if (sts < 0) {
// 如果system()返回<0,认为执行失败,抛出自定义异常
PyErr_SetString(SpamError, "System command failed");
returnNULL;
}
// 3.将整型sts转换为Python整数(PyLong)并返回
return PyLong_FromLong(sts);
}
-
参数解析:
PyArg_ParseTuple(args, "s", &command)
将Python元组参数转换为C字符串 -
系统调用:
system(command)
执行传入的命令 -
错误处理:命令执行失败时设置自定义异常
-
返回值转换:
PyLong_FromLong()
将C整型转换为Python对象
3.模块方法表
/* 模块方法表:将Python名称映射到C函数 */
static PyMethodDef spam_methods[] = {
{
"system", // Python端调用名
(PyCFunction) spam_system, // 对应的C函数指针
METH_VARARGS, // 接受位置参数 (tuple)
"Execute a shell command and return its exit status."// docstring
},
{NULL, NULL, 0, NULL} // 末尾哨兵
};
spam_methods
是一个 PyMethodDef
结构体数组,用于定义 Python 模块中的方法。每个元素描述一个可被 Python 调用的 C 函数,包括:
-
"system"
:Python 端调用的函数名(如spam.system
)。 -
(PyCFunction) spam_system
:对应的 C 实现函数指针。 -
METH_VARARGS
:表示该函数接受参数元组(即位置参数)。 -
"Execute a shell command and return its exit status."
:该方法的文档字符串。
数组最后一项 {NULL, NULL, 0, NULL}
是哨兵,用于标记方法表结束。这个表会在模块初始化时注册到 Python 解释器,使 Python 代码可以调用这些 C 函数。
4.模块初始化
该代码定义了模块初始化时的执行函数 spam_module_exec
,用于在 Python C 扩展模块加载时创建自定义异常类型,并将其添加到模块对象中,供 Python 端使用。
/*
* 模块执行时的初始化函数(Module Exec Slot)
* 在这里创建自定义异常,并把它添加到模块对象中(名字为SpamError)
*/
staticint
spam_module_exec(PyObject *m){
// 创建一个新的异常类spam.error,继承自Exception
SpamError = PyErr_NewException("spam.error", NULL, NULL);
if (SpamError == NULL)
return-1;
// 将异常对象添加到模块m中,名称为SpamError
if (PyModule_AddObjectRef(m, "SpamError", SpamError) < 0) {
Py_DECREF(SpamError); // 失败时减少引用计数
SpamError = NULL; // 防止悬空指针
return-1;
}
return0;
}
主要流程,如下所示:
-
通过
PyErr_NewException("spam.error", NULL, NULL)
创建一个新的异常类型spam.error
,并赋值给全局变量SpamError
。检查异常对象是否创建成功,失败则返回 -1。 -
使用
PyModule_AddObjectRef(m, "SpamError", SpamError)
将异常对象添加到模块中,名称为SpamError
,这样 Python 端可以通过spam.SpamError
访问。 -
如果添加失败,减少引用计数并清空指针,返回 -1。一切正常则返回 0,表示初始化成功。
这样做的目的是让模块有一个自定义异常类型,便于在 C 代码中抛出特定异常,Python 端可以捕获和处理。
5.模块定义结构
当 Python 导入 spam
模块时,会根据这些结构完成模块的注册、初始化自定义异常、方法绑定等工作。
static PyModuleDef_Slot spam_module_slots[] = {
{Py_mod_exec, spam_module_exec},
{0, NULL}
};
staticstructPyModuleDefspam_module = {
.m_base = PyModuleDef_HEAD_INIT,
.m_name = "spam",
.m_size = 0,
.m_methods = spam_methods,
.m_slots = spam_module_slots,
};
PyMODINIT_FUNC PyInit_spam(void){
return PyModuleDef_Init(&spam_module);
}
这段代码定义了 Python C 扩展模块的核心结构和初始化流程:
-
spam_module_slots
:模块执行槽数组,指定模块导入时要执行的函数(这里是spam_module_exec
,用于初始化自定义异常)。 -
spam_module
:模块定义结构体,包含模块名、文档字符串、方法表、执行槽、清理函数等信息。 -
PyInit_spam
:模块初始化函数,Python 导入模块时会调用,返回模块对象。
二.编译和使用
1.编译步骤
创建setup.py
文件:
from distutils.core import setup, Extension
module = Extension(
'spam',
sources=['spammodule.c']
)
setup(
name='spam',
version='1.0',
description='Python C Extension Example',
ext_modules=[module]
)

编译模块命令,如下所示:
python setup.py build
安装模块命令,如下所示:
python setup.py install
2.使用示例
import spam
# 成功执行命令
status = spam.system("ls -l")
print(f"Command exited with status: {status}")
# 测试自定义异常
try:
# 尝试执行不存在的命令
spam.system("nonexistent_command")
except spam.error as e:
print(f"Caught custom exception: {e}")
预期输出,如下所示:

Command exited with status: 0
Caught custom exception: System command failed
三.main()
方法解释
1.main()
方法
该段代码实现了一个既能作为 Python 扩展模块、又能作为可执行程序(嵌入 Python 解释器)的 C 程序。
/*
* 以下代码使得spammodule.c既能编译为Python扩展,也能编译成一个可执行程序(嵌入Python)
* int main(...) 演示了如何:
* 1) 在启动Python解释器之前,把spam模块加入内置模块表(PyImport_AppendInittab)
* 2) 初始化嵌入式Python解释器
* 3) 导入spammodule,然后调用spam.system()做示例演示
*/
intmain(int argc, char *argv[]){
PyStatus status;
PyConfig config;
int exit_code = 0; // 添加返回码变量
// 初始化Python配置(Python3.8及以上)
PyConfig_InitPythonConfig(&config);
/*
* 在初始化解释器之前,将spam模块注册到Python的内置模块表中
* 这样PyInit_spam()就会被Python运行时调用,创建并注册spam模块
*/
if (PyImport_AppendInittab("spam", PyInit_spam) == -1) {
fprintf(stderr, "Error: could not extend in-built modules table\n");
exit(1);
}
// 将argv[0]作为Python解释器的program_name
status = PyConfig_SetBytesString(&config, &config.program_name, argv[0]);
if (PyStatus_Exception(status)) {
PyConfig_Clear(&config);
return1;
}
// 初始化Python解释器
status = Py_InitializeFromConfig(&config);
if (PyStatus_Exception(status)) {
PyConfig_Clear(&config);
return1;
}
// 清除config(防止后面重复使用)
PyConfig_Clear(&config);
/*
* 到此为止,Python解释器已启动,并且spam模块以内置方式可用。可以:
* import spam
* spam.system("…")
*/
PyObject *pmodule = PyImport_ImportModule("spam");
if (!pmodule) {
PyErr_Print();
fprintf(stderr, "Error: could not import module 'spam'\n");
exit_code = 1; // 设置错误退出码
} else {
// 调用spam.system("echo Hello from embedded spam!")
PyObject *result = PyObject_CallMethod(pmodule,
"system",
"s",
"echo Hello from embedded spam!");
if (result) {
long sts = PyLong_AsLong(result);
printf(">>> spam.system returned: %ld\n", sts);
Py_DECREF(result);
} else {
PyErr_Print();
exit_code = 1; // 设置错误退出码
}
Py_DECREF(pmodule);
}
/* 关闭并清理 Python 解释器 */
Py_Finalize();
return exit_code; // 返回适当的退出码
}
其主要执行流程如下:
-
嵌入 Python 解释器:通过
main
函数,程序在 C 进程中初始化 Python 解释器。 -
注册自定义模块:在解释器初始化前,调用
PyImport_AppendInittab
把自定义的spam
模块注册到内置模块表,这样 Python 代码可以直接import spam
。 -
配置解释器参数:用
PyConfig
结构体设置解释器参数(如程序名)。 -
初始化解释器:调用
Py_InitializeFromConfig
启动 Python 解释器。 -
导入并调用模块方法:用
PyImport_ImportModule
导入spam
模块,然后用PyObject_CallMethod
调用spam.system("echo Hello from embedded spam!")
,并打印返回值。 -
错误处理:如果模块导入或方法调用失败,会打印 Python 错误信息并设置退出码。
-
清理资源:最后调用
Py_Finalize
关闭 Python 解释器,返回退出码。
2.CMakeLists.txt
文件
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 0002-spam/spammodule.c
Python/pythonrun.c
Python/pylifecycle.c
Python/ceval.c
Python/pyarena.c
0002-spam/spammodule.c)
# Define Py_BUILD_CORE
target_compile_definitions(CPythonExample PRIVATE Py_BUILD_CORE)
# link python library
target_link_libraries(CPythonExample python3.14 m)
3.输出结果

四.相关技术点
1.PyConfig_InitPythonConfig()
(1)PyConfig_InitPythonConfig(&config)
PyConfig config;
PyConfig_InitPythonConfig(&config);
这行代码用于初始化 PyConfig
结构体,为后续用 Py_InitializeFromConfig
以”配置驱动”方式启动 Python 解释器做准备。它会将 config
填充为默认值,确保后续对配置项的修改是安全的。
(2)和Py_Initialize()
区别
-
Py_Initialize()
是传统的初始化方式,使用全局默认配置,不能灵活定制启动参数。 -
Py_InitializeFromConfig()
(配合PyConfig_InitPythonConfig
)允许详细配置解释器启动参数(如模块路径、环境变量、I/O重定向等),适合嵌入式和高级场景。
总结:
-
Py_Initialize()
简单直接,适合大多数普通用例; -
PyConfig_InitPythonConfig
+Py_InitializeFromConfig()
提供更细粒度的控制,推荐用于嵌入式或需要自定义启动流程的场景。
2.PyConfig_SetBytesString()
status = PyConfig_SetBytesString(&config, &config.program_name, argv[0]);

-
作用是将 C 程序
argv[0]
(即程序自身的路径或名称)设置为 Python 解释器的program_name
配置项。这样做可以让嵌入式 Python 解释器知道当前程序的名称,有助于日志、错误信息和某些模块的行为一致性。 -
PyConfig_SetBytesString
是 Python C API(3.8+)用于安全设置配置字符串的函数,status
用于接收可能的错误状态。
3.PyConfig_Clear()
// 将argv[0]作为Python解释器的program_name
status = PyConfig_SetBytesString(&config, &config.program_name, argv[0]);
if (PyStatus_Exception(status)) {
PyConfig_Clear(&config);
return1;
}
在使用完 PyConfig
结构体后,调用 PyConfig_Clear
来释放其内部动态分配的资源,防止内存泄漏。
在本例中,如果 PyConfig_SetBytesString
失败(即 PyStatus_Exception(status)
为真),就会清理 config
,然后返回 1 退出。这样可以保证即使初始化失败也不会遗留内存资源。
4.Py_InitializeFromConfig()
status = Py_InitializeFromConfig(&config);
用于根据 config
结构体中的配置参数初始化嵌入式 Python 解释器。这样可以灵活地控制解释器的启动方式(如程序名、环境变量、模块路径等),适用于 Python 3.8 及以上版本。初始化后,C 程序就能运行 Python 代码和模块。返回值 status
用于判断初始化是否成功。
5.PyErr_Print()
当 Python C API 调用(如 PyObject_CallMethod
)失败时,当前的 Python 异常会被打印到标准错误输出(stderr)。这有助于调试,能显示出错的异常类型、信息和 traceback。
在本例中,如果 spam.system
调用失败,PyErr_Print();
会输出详细的 Python 错误信息,方便定位问题。
参考文献
[0] Python C扩展模块实例:spam:https://z0yrmerhgi8.feishu.cn/wiki/OfmZw6g0LiGKmDkeBumcAZycn8e
[1] 使用C或C++扩展Python:https://docs.python.org/zh-cn/3.13/extending/extending.html
[2] Python初始化配置:https://docs.python.org/zh-cn/3.14/c-api/init_config.html#
知识星球服务内容:Dify源码剖析及答疑,Dify对话系统源码,NLP电子书籍报告下载,公众号所有付费资料。加微信buxingtianxia21进NLP工程化资料群。
(文:NLP工程化)