核心原理
C语言调用Python的本质是:

- 启动一个Python解释器:在C程序中嵌入一个Python解释器实例。
- 使用C API与解释器交互:通过一系列预定义的C函数(在
Python.h中声明)来执行Python代码、调用Python函数、传递参数、获取返回值等。 - 数据类型转换:在C的数据类型(如
int,char*)和Python对象(如PyIntObject,PyStringObject)之间进行转换。 - 管理内存和引用计数:Python使用引用计数来管理内存,C代码必须正确地增加(
Py_INCREF)和减少(Py_DECREF)对象的引用计数,以避免内存泄漏或访问已释放的内存。
详细步骤
环境准备
你的系统必须安装了Python的开发头文件和库,这些通常在Python安装包中作为“开发包”或“开发库”提供。
-
在 Ubuntu/Debian 上:
sudo apt-get update sudo apt-get install python3-dev python3-pip # 对于 Python 3 # 或者 sudo apt-get install python-dev python-pip # 对于 Python 2 (已过时)
python3-dev包会包含Python.h和相关的链接库。 -
在 macOS 上 (使用 Homebrew):
(图片来源网络,侵删)brew install python
Homebrew的Python安装通常会自动配置好开发环境。
-
在 Windows 上: 确保你在安装Python时勾选了 "Add Python to PATH" 和 "Install for all users"(如果需要),编译时,你需要手动指定Python的包含目录和库目录。
- 包含目录:
C:\Users\YourUser\AppData\Local\Programs\Python\Python39\include - 库目录:
C:\Users\YourUser\AppData\Local\Programs\Python\Python39\libs
- 包含目录:
编写C代码
我们创建一个名为 call_python.c 的文件,这个程序将执行以下操作:
- 初始化Python解释器。
- 执行一段简单的Python代码(定义一个函数并调用它)。
- 调用一个Python函数,并传递C中的参数。
- 获取Python函数的返回值,并将其转换为C类型。
- 最终关闭Python解释器。
// call_python.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. 将C的命令行参数传递给Python
PySys_SetArgv(argc, argv);
// --- 示例1: 执行字符串形式的Python代码 ---
printf("--- Executing Python string ---\n");
PyRun_SimpleString("print('Hello from Python interpreter embedded in C!')");
printf("--- End of Python string execution ---\n\n");
// --- 示例2: 调用Python模块中的函数 ---
printf("--- Calling a Python function ---\n");
// 2.1 导入Python模块
// 假设我们有一个名为 'my_module.py' 的文件在同一目录下
PyObject *pName = PyUnicode_DecodeFSDefault("my_module"); // 模块名
PyObject *pModule = PyImport_Import(pName);
Py_DECREF(pName); // 导入完成后,减少模块名的引用计数
if (pModule != NULL) {
// 2.2 从模块中获取函数对象
PyObject *pFunc = PyObject_GetAttrString(pModule, "add_numbers"); // 函数名
if (pFunc && PyCallable_Check(pFunc)) {
// 2.3 准备函数参数
// PyObject *pArgs = PyTuple_New(2); // 创建一个包含2个元素的元组
// PyTuple_SetItem(pArgs, 0, PyLong_FromLong(10)); // 设置第一个参数
// PyTuple_SetItem(pArgs, 1, PyLong_FromLong(20)); // 设置第二个参数
// 更安全的参数构建方式
PyObject *pArgs = PyTuple_New(2);
PyObject *pValue1 = PyLong_FromLong(10);
PyObject *pValue2 = PyLong_FromLong(20);
PyTuple_SetItem(pArgs, 0, pValue1);
PyTuple_SetItem(pArgs, 1, pValue2);
// 注意: PyTuple_SetItem "steals" a reference, so no need to DECREF pValue1/2 here.
// 2.4 调用函数
PyObject *pResult = PyObject_CallObject(pFunc, pArgs);
Py_DECREF(pArgs); // 减少参数元组的引用
Py_DECREF(pFunc); // 减少函数对象的引用
if (pResult != NULL) {
// 2.5 处理返回值
long result = PyLong_AsLong(pResult);
printf("C: Python function returned %ld\n", result);
Py_DECREF(pResult);
} else {
PyErr_Print(); // 如果调用失败,打印Python错误信息
fprintf(stderr, "Call failed\n");
}
} else {
if (PyErr_Occurred()) PyErr_Print();
fprintf(stderr, "Cannot find function \"add_numbers\"\n");
}
Py_DECREF(pModule);
} else {
PyErr_Print();
fprintf(stderr, "Failed to load module \"my_module\"\n");
}
// 3. 关闭Python解释器
Py_Finalize();
return 0;
}
创建Python模块
在同一目录下,创建一个名为 my_module.py 的文件,这个文件将被上面的C程序调用。

# my_module.py
def add_numbers(a, b):
"""Adds two numbers and prints them."""
print(f"Python: Adding {a} and {b}")
return a + b
def greet(name):
"""Greets a person."""
return f"Hello, {name}!"
编译C代码
你需要使用C编译器(如 gcc)来编译你的代码,并链接Python的库。
-
在 Linux/macOS 上:
# -o 指定输出文件名 # $(python3-config --cflags) 自动获取Python的编译器标志(如包含路径) # $(python3-config --ldflags) 自动获取Python的链接器标志(如库路径和库名) gcc call_python.c -o call_python $(python3-config --cflags) $(python3-config --ldflags)
-
在 Windows 上 (使用 MinGW/gcc):
你需要手动指定路径,假设你的Python安装在
C:\Python39:gcc call_python.c -o call_python.exe -IC:\Python39\include -LC:\Python39\libs -lpython39
-IC:\Python39\include: 指定头文件路径。-LC:\Python39\libs: 指定库文件路径。-lpython39: 链接python39.lib库,库名是python+ 版本号。
运行程序
编译成功后,运行生成的可执行文件:
./call_python
预期输出:
--- Executing Python string ---
Hello from Python interpreter embedded in C!
--- End of Python string execution ---
--- Calling a Python function ---
Python: Adding 10 and 20
C: Python function returned 30
高级主题与最佳实践
错误处理
Python API中的很多函数在失败时会返回 NULL,你应该始终检查这些返回值,并使用 PyErr_Print() 来打印Python层面的错误堆栈信息,这对于调试至关重要。
内存管理 (Py_INCREF 和 Py_DECREF)
这是最容易出错的地方。规则是:任何从Python API获得一个引用(指针)的函数,都必须负责在不再需要时减少它的引用计数。
- 借用引用:有些函数返回的引用是“借用”的,
PyObject_GetAttrString(pModule, "name")pModule被销毁,这个借用引用就无效了,在这种情况下,你不应该调用Py_DECREF。 - 新引用:像
PyImport_Import(),PyLong_FromLong(),PyTuple_New()等函数返回的都是新引用,你必须调用Py_DECREF。 - 规则:当你不确定时,假设返回的是新引用,并在使用后
Py_DECREF,查阅官方文档是最好的方法。
数据类型转换
Python API提供了丰富的函数来在C类型和Python对象之间转换:
- 数字:
PyLong_FromLong(long)PyLong_AsLong(PyObject*)PyFloat_FromDouble(double)PyFloat_AsDouble(PyObject*)
- 字符串:
PyUnicode_FromString(const char*)(创建Python Unicode字符串)PyUnicode_AsUTF8(PyObject*)(获取UTF-8编码的C字符串,注意:返回的指针仅在原对象存在时有效)PyBytes_AsString(PyObject*)(获取字节串的C字符串)
- 列表/元组:
PyList_New(Py_ssize_t size)PyList_SetItem(PyObject *list, Py_ssize_t index, PyObject *item)(steals reference)PyList_GetItem(PyObject *list, Py_ssize_t index)(returns borrowed reference)PyTuple_...(类似列表)
从C传递回调函数给Python
这是一个更高级的用法,允许你将C函数作为参数传递给Python函数,这需要创建一个新的C函数类型,并将其包装成一个Python可调用对象,实现起来比较复杂,通常可以使用 Cython 等工具来简化。
替代方案:Python C扩展
如果你不是想在C程序中嵌入Python解释器,而是想用C语言编写模块来扩展Python的功能(即让Python代码可以调用你的C函数),那么你应该编写一个Python C扩展,这与“C调用Python”是相反的方向。
- 方法:创建一个C文件,实现一个模块初始化函数 (
my_module_init),然后使用distutils或setuptools的setup.py来编译它。 - 优点:性能更高,是Python标准库和许多高性能库(如NumPy, Pandas)的实现方式。
- 缺点:开发周期更长,调试更困难。
替代方案:使用第三方库
为了简化C和Python的互操作,出现了一些优秀的第三方库:
-
Cython:
- 简介:一个超集的Python语言,可以编译成高效的C/C++代码,你可以用它来包装C/C++库,或者将纯Python代码优化成C扩展。
- 优点:语法接近Python,极大地简化了编写C扩展的难度,是目前最流行和推荐的方式。
-
pybind11:
- 简介:一个轻量级的仅头文件的C++库,用于将C++代码绑定到Python。
- 优点:现代C++风格,API直观易用,模板化设计非常灵活,如果你在用C++,这是首选。
-
Boost.Python:
- 简介:一个功能强大的C++库,用于Python绑定。
- 优点:功能全面,历史悠久。
- 缺点:依赖Boost库,比较笨重。
| 方法 | 核心思想 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|---|
| Python C API | C程序嵌入并调用Python解释器 | 灵活,功能强大,无需额外依赖 | 复杂,手动内存管理,易出错 | 需要将Python脚本集成到现有C应用程序中,或利用Python库进行特定计算。 |
| Python C扩展 | 编写C代码,供Python调用 | 性能极高,与Python无缝集成 | 开发复杂,调试困难 | 编写高性能的Python模块,如NumPy的核心算法。 |
| Cython | 将Python/Cython代码编译成C模块 | 简单易学,性能好,生态成熟 | 需要学习Cython语法,编译步骤 | 为C/C++库创建Python包装,或优化性能瓶颈的Python代码。 |
| pybind11 | 用现代C++轻松绑定Python | API简洁,现代C++风格,易用 | 仅限C++ | 快速将C++库暴露给Python,编写高性能的Python扩展。 |
对于大多数新项目,如果你需要在C/C++中使用Python,直接使用Python C API是可行的,但如果你是在用C++,强烈推荐 pybind11,如果你需要从Python调用C/C++,Cython 是最简单、最推荐的选择。
