C语言如何直接调用Python模块?

99ANYc3cd6
预计阅读时长 30 分钟
位置: 首页 C语言 正文

核心概念

  1. Python解释器: 你的C程序需要嵌入一个Python解释器实例,这个解释器会执行你的Python代码。
  2. Python C API: 这是Python官方提供的一组C语言函数和数据结构,用于与Python解释器交互,你可以用它来执行Python代码、调用函数、操作对象等。
  3. 数据类型转换: C语言和Python的数据类型是不同的,C的 int 对应Python的 int 对象,C的 char* 对应Python的 bytesstr 对象,API提供了函数在这些类型之间进行转换。
  4. 模块路径: Python解释器需要知道去哪里找你的自定义模块,你需要确保Python的模块搜索路径(sys.path)包含了你的模块所在目录。

准备工作

  1. 安装Python开发包: 这是最关键的一步,你需要的不仅仅是Python解释器,还包括它的头文件(.h)和库文件(.lib.so / .dylib)。

    c语言调用python模块
    (图片来源网络,侵删)
    • 在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.hpython3.lib 才会被正确安装,通常它们位于 Python\IncludePython\Libs 目录下。
  2. 一个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函数。

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的包含目录和库目录。

c语言调用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

  1. 引用计数: 在Python C API中,几乎所有的对象都使用引用计数来管理内存。

    • Py_INCREF(obj): 增加引用计数。
    • Py_DECREF(obj): 减少引用计数,如果计数归零,对象会被销毁。
    • 当你通过API(如 PyImport_Import, PyLong_FromLong)获得一个新对象时,你获得了一个引用,必须在使用完毕后 Py_DECREF
    • 当你将一个对象传递给API(如 PyTuple_SetItem)时,API会“窃取”你的引用,因此你不需要Py_DECREFPyTuple_SetItem 是个特例,它总是窃取引用,而 PyList_Append 不会,它会增加引用计数,调用者需要负责释放。
  2. 错误处理: 很多API函数在失败时会返回 NULL 并设置一个Python异常,你必须检查返回值,并在必要时调用 PyErr_Print() 来打印Python级别的错误堆栈,或者使用 PyErr_ExceptionMatchesPyErr_Clear 进行更精细的处理。

  3. GIL (全局解释器锁):

    • Python的GIL确保同一时刻只有一个线程能执行Python字节码。
    • 当你的C代码通过Python C API调用Python代码时,你必须持有GIL
    • Py_Initialize() 会自动获取主线程的GIL。
    • 如果你创建了自己的C线程来调用Python,你需要使用 PyGILState_Ensure()PyGILState_Release() 来手动管理GIL。
  4. 现代替代方案: 虽然Python C API非常强大,但它也相当复杂且容易出错,对于许多现代应用,可以考虑以下更高级的替代方案:

    • Cython: 将Python/Cython代码编译成C扩展模块,然后在C中像调用普通动态库一样调用它,这是目前最流行和推荐的方式之一。
    • PyO3: 一个Rust库,用于创建与Python交互的扩展,它提供了更安全、更现代的绑定,虽然它是Rust库,但它展示了现代绑定工具的设计理念,对理解C API也有帮助。
    • Python.h的C++封装: 如 Boost.Python,提供了更面向对象的接口。

希望这份详细的指南能帮助你成功地在C语言中调用Python模块!

-- 展开阅读全文 --
头像
dede文章页背景如何制作?
« 上一篇 03-04
织梦后台模板路径怎么找?
下一篇 » 03-04

相关文章

取消
微信二维码
支付宝二维码

目录[+]