核心概念
- Python解释器: 你的C程序需要嵌入一个Python解释器实例,这个解释器会执行你的Python代码。
- Python C API: 这是Python官方提供的一组C语言函数和数据结构,用于与Python解释器交互,你可以用它来执行Python代码、调用函数、操作对象等。
- 数据类型转换: C语言和Python的数据类型是不同的,C的
int对应Python的int对象,C的char*对应Python的bytes或str对象,API提供了函数在这些类型之间进行转换。 - 模块路径: Python解释器需要知道去哪里找你的自定义模块,你需要确保Python的模块搜索路径(
sys.path)包含了你的模块所在目录。
准备工作
-
安装Python开发包: 这是最关键的一步,你需要的不仅仅是Python解释器,还包括它的头文件(
.h)和库文件(.lib或.so/.dylib)。
(图片来源网络,侵删)- 在Ubuntu/Debian上:
sudo apt-get update sudo apt-get install python3-dev
- 在CentOS/RHEL上:
sudo yum install python3-devel
- 在macOS上 (使用Homebrew):
brew install python3
- 在Windows上:
在安装Python时,请务必勾选 "Install development libraries" 或 "为所有用户安装" 选项,这样
python.h和python3.lib才会被正确安装,通常它们位于Python\Include和Python\Libs目录下。
- 在Ubuntu/Debian上:
-
一个C编译器: 例如GCC (Linux/macOS) 或 MSVC (Windows)。
调用一个简单的Python函数(无参数)
这个场景将展示如何初始化Python环境,调用一个不接收任何参数并返回简单值的Python函数。
创建Python模块 (my_module.py)
创建一个名为 my_module.py 的文件,里面包含一个我们想要从C调用的函数。
# my_module.py
def say_hello():
"""一个简单的Python函数,打印一条消息并返回一个字符串。"""
print("Hello from Python function 'say_hello'!")
return "This is a return value from Python."
编写C代码 (main.c)
创建一个 main.c 文件来调用上面的Python函数。

(图片来源网络,侵删)
// main.c
#include <Python.h> // 必须包含的头文件
int main(int argc, char *argv[]) {
// 1. 初始化Python解释器
Py_Initialize();
if (!Py_IsInitialized()) {
fprintf(stderr, "Error: Failed to initialize Python interpreter.\n");
return 1;
}
// 2. 将当前目录添加到Python的模块搜索路径中
// 这样Python解释器才能找到 my_module.py
PyRun_SimpleString("import sys");
PyRun_SimpleString("sys.path.append('.')");
// 3. 导入Python模块
PyObject *pName = PyUnicode_DecodeFSDefault("my_module");
if (!pName) {
PyErr_Print();
fprintf(stderr, "Error: Failed to create module name.\n");
Py_Finalize();
return 1;
}
PyObject *pModule = PyImport_Import(pName);
Py_DECREF(pName); // 引用计数减一,释放pName
if (!pModule) {
PyErr_Print();
fprintf(stderr, "Error: Failed to import module 'my_module'.\n");
Py_Finalize();
return 1;
}
// 4. 从模块中获取函数对象
PyObject *pFunc = PyObject_GetAttrString(pModule, "say_hello");
if (!pFunc || !PyCallable_Check(pFunc)) {
if (PyErr_Occurred()) PyErr_Print();
fprintf(stderr, "Error: Cannot find function 'say_hello' or it's not callable.\n");
Py_DECREF(pModule);
Py_Finalize();
return 1;
}
// 5. 调用Python函数 (无参数)
PyObject *pArgs = PyTuple_New(0); // 创建一个空参数元组
PyObject *pValue = PyObject_CallObject(pFunc, pArgs);
Py_DECREF(pArgs); // 释放参数元组
if (pValue != NULL) {
// 6. 处理返回值
printf("C: Received return value from Python.\n");
// 假设返回值是一个字符串
if (PyUnicode_Check(pValue)) {
const char *result = PyUnicode_AsUTF8(pValue);
printf("C: Return value is: '%s'\n", result);
}
Py_DECREF(pValue); // 释放返回值对象
} else {
PyErr_Print();
fprintf(stderr, "Error: Function call failed.\n");
}
// 7. 清理
Py_DECREF(pFunc);
Py_DECREF(pModule);
Py_Finalize();
return 0;
}
编译和运行
在Linux/macOS上:
你需要链接Python的库。pkg-config 是一个非常方便的工具。
# 首先安装 pkg-config (如果还没有) # Ubuntu/Debian: sudo apt-get install pkg-config # macOS (with Homebrew): brew install pkg-config # 使用 pkg-config 获取编译和链接标志 # 注意:如果你的系统有多个Python版本,可能需要指定 python-3.10 等版本 CFLAGS=$(pkg-config --cflags python3) LDFLAGS=$(pkg-config --libs python3) # 编译 gcc $(CFLAGS) main.c -o c_call_python $(LDFLAGS) # 运行 ./c_call_python
在Windows上 (使用Visual Studio Developer Command Prompt):
你需要手动指定Python的包含目录和库目录。

(图片来源网络,侵删)
# 假设你的Python安装在 C:\Users\YourUser\AppData\Local\Programs\Python\Python311 set PYTHON_INCLUDE=C:\Users\YourUser\AppData\Local\Programs\Python\Python311\include set PYTHON_LIB=C:\Users\YourUser\AppData\Local\Programs\Python\Python311\libs # 编译 cl /I "%PYTHON_INCLUDE%" main.c /link /LIBPATH:"%PYTHON_LIB%" python311.lib # 运行 c_call_python.exe
预期输出:
Hello from Python function 'say_hello'!
C: Received return value from Python.
C: Return value is: 'This is a return value from Python.'
调用带参数的Python函数
现在我们扩展一下,看看如何传递参数并获取更复杂的返回值。
修改Python模块 (my_module.py)
# my_module.py
def add_numbers(a, b):
"""接收两个数字,返回它们的和。"""
print(f"Python: Adding {a} and {b}")
return a + b
def process_data(data_list):
"""接收一个列表,返回列表中所有数字的和。"""
print(f"Python: Received list: {data_list}")
return sum(data_list)
修改C代码 (main.c)
我们将在主函数中添加调用这两个新函数的代码。
// ... (前面的代码与场景一相同,直到调用 say_hello 之后) ...
// --- 调用 add_numbers ---
printf("\n--- Calling add_numbers ---\n");
pFunc = PyObject_GetAttrString(pModule, "add_numbers");
if (pFunc && PyCallable_Check(pFunc)) {
// 准备参数: (a, b) -> (10, 20)
PyObject *pArgs = PyTuple_New(2);
PyTuple_SetItem(pArgs, 0, PyLong_FromLong(10)); // 参数1: 10
PyTuple_SetItem(pArgs, 1, PyLong_FromLong(20)); // 参数2: 20
PyObject *pValue = PyObject_CallObject(pFunc, pArgs);
Py_DECREF(pArgs);
if (pValue != NULL) {
long result = PyLong_AsLong(pValue);
printf("C: The sum is %ld\n", result);
Py_DECREF(pValue);
} else {
PyErr_Print();
fprintf(stderr, "Error: Call to add_numbers failed.\n");
}
}
Py_DECREF(pFunc);
// --- 调用 process_data ---
printf("\n--- Calling process_data ---\n");
pFunc = PyObject_GetAttrString(pModule, "process_data");
if (pFunc && PyCallable_Check(pFunc)) {
// 准备参数: ([1, 2, 3, 4, 5],)
PyObject *pList = PyList_New(5);
for (int i = 0; i < 5; i++) {
PyList_SetItem(pList, i, PyLong_FromLong(i + 1));
}
PyObject *pArgs = PyTuple_New(1);
PyTuple_SetItem(pArgs, 0, pList); // 将列表作为唯一参数
PyObject *pValue = PyObject_CallObject(pFunc, pArgs);
Py_DECREF(pArgs);
if (pValue != NULL) {
long result = PyLong_AsLong(pValue);
printf("C: The sum of the list is %ld\n", result);
Py_DECREF(pValue);
} else {
PyErr_Print();
fprintf(stderr, "Error: Call to process_data failed.\n");
}
}
Py_DECREF(pFunc);
// ... (最后的清理代码与场景一相同) ...
编译和运行
编译命令与场景一相同,运行后,你会看到:
Hello from Python function 'say_hello'!
C: Received return value from Python.
C: Return value is: 'This is a return value from Python.'
--- Calling add_numbers ---
Python: Adding 10 and 20
C: The sum is 30
--- Calling process_data ---
Python: Received list: [1, 2, 3, 4, 5]
C: The sum of the list is 15
-
引用计数: 在Python C API中,几乎所有的对象都使用引用计数来管理内存。
Py_INCREF(obj): 增加引用计数。Py_DECREF(obj): 减少引用计数,如果计数归零,对象会被销毁。- 当你通过API(如
PyImport_Import,PyLong_FromLong)获得一个新对象时,你获得了一个引用,必须在使用完毕后Py_DECREF。 - 当你将一个对象传递给API(如
PyTuple_SetItem)时,API会“窃取”你的引用,因此你不需要再Py_DECREF。PyTuple_SetItem是个特例,它总是窃取引用,而PyList_Append不会,它会增加引用计数,调用者需要负责释放。
-
错误处理: 很多API函数在失败时会返回
NULL并设置一个Python异常,你必须检查返回值,并在必要时调用PyErr_Print()来打印Python级别的错误堆栈,或者使用PyErr_ExceptionMatches和PyErr_Clear进行更精细的处理。 -
GIL (全局解释器锁):
- Python的GIL确保同一时刻只有一个线程能执行Python字节码。
- 当你的C代码通过Python C API调用Python代码时,你必须持有GIL。
Py_Initialize()会自动获取主线程的GIL。- 如果你创建了自己的C线程来调用Python,你需要使用
PyGILState_Ensure()和PyGILState_Release()来手动管理GIL。
-
现代替代方案: 虽然Python C API非常强大,但它也相当复杂且容易出错,对于许多现代应用,可以考虑以下更高级的替代方案:
- Cython: 将Python/Cython代码编译成C扩展模块,然后在C中像调用普通动态库一样调用它,这是目前最流行和推荐的方式之一。
- PyO3: 一个Rust库,用于创建与Python交互的扩展,它提供了更安全、更现代的绑定,虽然它是Rust库,但它展示了现代绑定工具的设计理念,对理解C API也有帮助。
- Python.h的C++封装: 如
Boost.Python,提供了更面向对象的接口。
希望这份详细的指南能帮助你成功地在C语言中调用Python模块!
