核心思想是使用 Python/C API,这是一个由 Python 解释器提供的 C 语言接口,允许 C 程序调用 Python 函数、操作 Python 对象,并嵌入 Python 解释器。

下面我将从简单到复杂,分步讲解如何实现。
直接执行 Python 字符串代码 (最简单)
这种方法适用于执行一小段动态的 Python 代码。
核心步骤:
- 初始化 Python 解释器 (
Py_Initialize)。 - 执行 Python 代码 (
PyRun_SimpleString)。 - 关闭 Python 解释器 (
Py_Finalize)。
代码示例 (C)
// my_c_program.c
#include <Python.h>
int main(int argc, char *argv[]) {
// 1. 初始化 Python 解释器
Py_Initialize();
if (!Py_IsInitialized()) {
fprintf(stderr, "Error: Failed to initialize the Python interpreter.\n");
return 1;
}
// 2. 执行 Python 代码字符串
// - 可以执行简单的表达式
PyRun_SimpleString("print('Hello from Python!')");
// - 也可以执行多行代码块
PyRun_SimpleString("a = 10\n"
"b = 20\n"
"print(f'The sum of a and b is: {a + b}')");
// 3. 关闭 Python 解释器,释放资源
Py_Finalize();
printf("C program finished.\n");
return 0;
}
如何编译和运行?
你需要先安装 Python 开发头文件和库,在 Linux 上,通常使用 python3-dev 或 python3-devel 包。
安装依赖 (以 Ubuntu/Debian 为例)

sudo apt-get update sudo apt-get install python3-dev
编译 C 代码
你需要告诉编译器去哪里找 Python 的头文件和库文件。python3-config 命令可以方便地获取这些信息。
# 使用 python3-config 自动获取编译参数 gcc my_c_program.c -o my_c_program $(python3-config --cflags --ldflags)
--cflags: 获取编译所需的 C 编译器标志(如头文件路径-I)。--ldflags: 获取链接所需的库文件标志(如库路径-L和库名-lpython3.x)。
运行
./my_c_program
预期输出:
Hello from Python!
The sum of a and b is: 30
C program finished.
执行一个 Python 文件 (更实用)
这种方法更常见,即 C 程序调用一个外部的 .py 脚本。
核心步骤:
- 初始化 Python 解释器 (
Py_Initialize)。 - 将 Python 脚本所在的目录添加到 Python 的模块搜索路径 (
sys.path) 中,这样 Python 才能找到你的脚本。 - 使用
PyImport_ImportModule导入 Python 模块(即你的.py文件)。 - 获取模块中的函数 (
PyObject_GetAttrString)。 - 调用函数 (
PyObject_CallObject)。 - 处理返回值和异常。
- 关闭 Python 解释器 (
Py_Finalize)。
代码示例 (C)
假设我们有以下 Python 文件:
my_python_script.py
# my_python_script.py
import sys
def greet(name):
"""Prints a greeting and returns a message."""
print(f"Python: Hello, {name}!")
return f"Greeting sent to {name}."
def add_numbers(a, b):
"""Adds two numbers and returns the result."""
if not isinstance(a, (int, float)) or not isinstance(b, (int, float)):
raise TypeError("Both arguments must be numbers.")
return a + b
main.c
// main.c
#include <Python.h>
#include <stdio.h>
int main(int argc, char *argv[]) {
Py_Initialize();
if (!Py_IsInitialized()) {
fprintf(stderr, "Error: Failed to initialize the Python interpreter.\n");
return 1;
}
// 将当前目录添加到 Python 的模块搜索路径
PyRun_SimpleString("import sys");
PyRun_SimpleString("sys.path.append('.')");
// 导入 Python 模块
PyObject* pName = PyUnicode_DecodeFSDefault("my_python_script"); // 模块名
PyObject* pModule = PyImport_Import(pName);
Py_DECREF(pName);
if (pModule != NULL) {
// --- 调用 greet 函数 ---
PyObject* pFunc = PyObject_GetAttrString(pModule, "greet");
if (pFunc && PyCallable_Check(pFunc)) {
PyObject* pArgs = PyTuple_New(1);
PyTuple_SetItem(pArgs, 0, PyUnicode_FromString("C World")); // 参数 "C World"
PyObject* pValue = PyObject_CallObject(pFunc, pArgs);
Py_DECREF(pArgs);
if (pValue != NULL) {
// 处理 greet 的返回值
printf("C: Received return value from greet: %s\n", PyUnicode_AsUTF8(pValue));
Py_DECREF(pValue);
} else {
PyErr_Print(); // 打印 Python 异常
}
} else {
if (PyErr_Occurred()) PyErr_Print();
fprintf(stderr, "Cannot find function greet\n");
}
Py_XDECREF(pFunc);
// --- 调用 add_numbers 函数 ---
PyObject* pFuncAdd = PyObject_GetAttrString(pModule, "add_numbers");
if (pFuncAdd && PyCallable_Check(pFuncAdd)) {
PyObject* pArgsAdd = PyTuple_New(2);
PyTuple_SetItem(pArgsAdd, 0, PyLong_FromLong(15)); // 参数 15
PyTuple_SetItem(pArgsAdd, 1, PyLong_FromLong(27)); // 参数 27
PyObject* pValueAdd = PyObject_CallObject(pFuncAdd, pArgsAdd);
Py_DECREF(pArgsAdd);
if (pValueAdd != NULL) {
// 处理 add_numbers 的返回值
long result = PyLong_AsLong(pValueAdd);
printf("C: Received return value from add_numbers: %ld\n", result);
Py_DECREF(pValueAdd);
} else {
PyErr_Print(); // Python 函数内部可能会抛出异常
}
} else {
if (PyErr_Occurred()) PyErr_Print();
fprintf(stderr, "Cannot find function add_numbers\n");
}
Py_XDECREF(pFuncAdd);
} else {
PyErr_Print();
fprintf(stderr, "Failed to load module my_python_script\n");
}
Py_XDECREF(pModule);
Py_Finalize();
return 0;
}
如何编译和运行?
编译命令和之前一样:
gcc main.c -o my_c_app $(python3-config --cflags --ldflags)
运行
./my_c_app
预期输出:
Python: Hello, C World!
C: Received return value from greet: Greeting sent to C World.
C: Received return value from add_numbers: 42
关键点与注意事项
-
内存管理: Python/C API 中的对象都是通过引用计数管理的,当你创建一个新对象(如
PyUnicode_FromString)或获取一个已有对象的引用(如PyObject_GetAttrString)时,它的引用计数会增加,当你使用完这个对象后,必须调用Py_DECREF来减少引用计数,当计数归零时,内存会被自动释放,忘记Py_DECREF会导致内存泄漏。Py_XDECREF是一个更安全的版本,它会在对象不为NULL时才进行DecRef。 -
错误处理: Python 函数可能会抛出异常,在 C 端调用后,必须检查返回值是否为
NULL,如果为NULL,通常意味着发生了异常,此时应调用PyErr_Print()来打印 Python 级别的错误堆栈信息,否则错误信息会丢失。 -
数据类型转换: C 语言和 Python 的数据类型是不同的,API 提供了转换函数:
- C
int-> Pythonint:PyLong_FromLong() - Python
int-> Clong:PyLong_AsLong() - C
char*-> Pythonstr:PyUnicode_FromString() - Python
str-> Cchar*:PyUnicode_AsUTF8() - 创建 Python 元组:
PyTuple_New(),PyTuple_SetItem()
- C
-
GIL (全局解释器锁): 当你嵌入 Python 解释器时,默认情况下你会获得 GIL,这意味着在调用 Python 代码时,你的 C 线程是独占解释器的,如果你的 C 程序是多线程的,并且希望其他线程也能同时执行 Python 代码,你需要使用
PyGILState_Ensure()和PyGILState_Release()来管理 GIL 的状态。 -
性能: 在 C 和 Python 之间频繁地传递数据(尤其是在循环中)会有性能开销,因为涉及类型转换和 API 调用,对于性能敏感的场景,最好将大量的计算逻辑放在 Python 端,C 端只负责启动和最终结果的获取。
| 方法 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
PyRun_SimpleString |
执行少量、固定的 Python 代码。 | 简单直接,无需外部文件。 | 代码难以管理,无法复用,功能有限。 |
| Python/C API 调用模块 | C 程序需要调用一个或多个独立的 Python 脚本/模块。 | 功能强大,代码结构清晰,易于维护和复用 Python 代码。 | 实现较复杂,需要处理内存管理和错误检查。 |
对于绝大多数应用场景,方法二(调用 Python 模块) 是更推荐和更实用的方案,它将 C 和 Python 的职责清晰地分开,使得代码更易于维护和扩展。
