C语言如何调用Python?

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

核心原理

C语言调用Python的本质是:

c语言如何调用python
(图片来源网络,侵删)
  1. 启动一个Python解释器:在C程序中嵌入一个Python解释器实例。
  2. 使用C API与解释器交互:通过一系列预定义的C函数(在 Python.h 中声明)来执行Python代码、调用Python函数、传递参数、获取返回值等。
  3. 数据类型转换:在C的数据类型(如 int, char*)和Python对象(如 PyIntObject, PyStringObject)之间进行转换。
  4. 管理内存和引用计数: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):

    c语言如何调用python
    (图片来源网络,侵删)
    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 的文件,这个程序将执行以下操作:

  1. 初始化Python解释器。
  2. 执行一段简单的Python代码(定义一个函数并调用它)。
  3. 调用一个Python函数,并传递C中的参数。
  4. 获取Python函数的返回值,并将其转换为C类型。
  5. 最终关闭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程序调用。

c语言如何调用python
(图片来源网络,侵删)
# 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_INCREFPy_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),然后使用 distutilssetuptoolssetup.py 来编译它。
  • 优点:性能更高,是Python标准库和许多高性能库(如NumPy, Pandas)的实现方式。
  • 缺点:开发周期更长,调试更困难。

替代方案:使用第三方库

为了简化C和Python的互操作,出现了一些优秀的第三方库:

  1. Cython:

    • 简介:一个超集的Python语言,可以编译成高效的C/C++代码,你可以用它来包装C/C++库,或者将纯Python代码优化成C扩展。
    • 优点:语法接近Python,极大地简化了编写C扩展的难度,是目前最流行和推荐的方式。
  2. pybind11:

    • 简介:一个轻量级的仅头文件的C++库,用于将C++代码绑定到Python。
    • 优点:现代C++风格,API直观易用,模板化设计非常灵活,如果你在用C++,这是首选。
  3. 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 是最简单、最推荐的选择。

-- 展开阅读全文 --
头像
织梦地图插件如何快速适配普通地图功能?
« 上一篇 03-04
dede自定义内容模型如何创建与使用?
下一篇 » 03-04
取消
微信二维码
支付宝二维码

目录[+]