C语言如何调用Python脚本?

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

C语言程序通过Python的C API(一个头文件和一组库函数)来嵌入一个Python解释器,这使得C程序可以执行Python代码、调用Python函数、并传递和接收数据。

c语言调用python脚本
(图片来源网络,侵删)

下面我将从最简单到最复杂的场景,分步讲解如何实现。

准备工作

  1. 安装Python:确保你的系统上已经安装了Python,你需要知道Python的头文件(.h)和库文件(.lib.so/.dylib)的位置。
  2. 获取Python路径
    • 在Windows上,Python安装路径通常就是你需要包含的路径和链接的库路径。
    • 在Linux/macOS上,你可以使用以下命令找到路径:
      # 查找头文件路径
      python3-config --includes
      # 查找库文件路径
      python3-config --ldflags

在C中执行一段简单的Python代码

这是最基础的用法,C程序直接执行一段Python脚本字符串。

步骤:

  1. 包含Python头文件#include <Python.h>
  2. 初始化Python解释器Py_Initialize()
  3. 执行Python代码PyRun_SimpleString()
  4. 关闭Python解释器Py_Finalize()

示例代码 (main.c)

#include <Python.h>
#include <stdio.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代码
    // 打印 "Hello from Python!"
    PyRun_SimpleString("print('Hello from Python!')");
    // 3. 执行一个稍微复杂的Python代码
    PyRun_SimpleString("a = 10\n"
                       "b = 20\n"
                       "print(f'The sum of a and b is: {a + b}')");
    // 4. 关闭Python解释器
    Py_Finalize();
    printf("C program finished.\n");
    return 0;
}

如何编译和运行 (以Linux为例)

你需要链接Python的库,使用 python3-config --ldflags 可以得到所需的链接参数。

# 编译
gcc main.c -o main $(python3-config --ldflags)
# 运行
./main

预期输出:

c语言调用python脚本
(图片来源网络,侵删)
Hello from Python!
The sum of a and b is: 30
C program finished.

调用Python脚本中的函数并传递参数

这是更实用的场景,C程序调用一个独立的 .py 文件中的函数。

Python脚本 (my_module.py)

这个脚本定义了一个函数,可以接收参数并返回结果。

# my_module.py
def greet(name):
    """返回一个问候语"""
    return f"Hello, {name}! Welcome to the world of C and Python."
def add_numbers(a, b):
    """返回两个数的和"""
    return a + b

C语言程序 (call_func.c)

C程序需要:

  1. 导入Python模块 (my_module)。
  2. 将C数据类型转换为Python对象 (PyUnicode_FromString, PyLong_FromLong)。
  3. 调用Python函数 (PyObject_CallObject)。
  4. 将Python返回结果转换回C数据类型 (PyUnicode_AsUTF8, PyLong_AsLong)。
  5. 记得释放所有创建的Python对象 (Py_DECREF),以避免内存泄漏。
#include <Python.h>
#include <stdio.h>
int main(int argc, char *argv[]) {
    Py_Initialize();
    // 1. 导入Python模块
    PyObject *pName = PyUnicode_DecodeFSDefault("my_module"); // 模块名
    PyObject *pModule = PyImport_Import(pName);
    Py_DECREF(pName);
    if (!pModule) {
        PyErr_Print();
        fprintf(stderr, "Error importing module my_module\n");
        return 1;
    }
    // --- 调用 greet("Alice") 函数 ---
    // 2. 获取模块中的函数
    PyObject *pGreetFunc = PyObject_GetAttrString(pModule, "greet");
    if (!pGreetFunc || !PyCallable_Check(pGreetFunc)) {
        if (PyErr_Occurred()) PyErr_Print();
        fprintf(stderr, "Cannot find function greet\n");
        Py_DECREF(pModule);
        return 1;
    }
    // 3. 准备参数 (必须是元组)
    PyObject *pArgs = PyTuple_New(1);
    PyObject *pValue = PyUnicode_FromString("Alice");
    PyTuple_SetItem(pArgs, 0, pValue); // 注意: SetItem "steals" a reference, so no DECREF needed here
    // 4. 调用函数
    PyObject *pGreetResult = PyObject_CallObject(pGreetFunc, pArgs);
    Py_DECREF(pArgs);
    Py_DECREF(pGreetFunc);
    if (pGreetResult != NULL) {
        // 5. 处理返回结果
        const char *result_str = PyUnicode_AsUTF8(pGreetResult);
        printf("C received from Python: %s\n", result_str);
        Py_DECREF(pGreetResult);
    } else {
        PyErr_Print();
        fprintf(stderr, "Call failed\n");
    }
    // --- 调用 add_numbers(5, 7) 函数 ---
    PyObject *pAddFunc = PyObject_GetAttrString(pModule, "add_numbers");
    if (!pAddFunc || !PyCallable_Check(pAddFunc)) {
        if (PyErr_Occurred()) PyErr_Print();
        fprintf(stderr, "Cannot find function add_numbers\n");
        Py_DECREF(pModule);
        return 1;
    }
    pArgs = PyTuple_New(2);
    PyTuple_SetItem(pArgs, 0, PyLong_FromLong(5));
    PyTuple_SetItem(pArgs, 1, PyLong_FromLong(7));
    PyObject *pAddResult = PyObject_CallObject(pAddFunc, pArgs);
    Py_DECREF(pArgs);
    Py_DECREF(pAddFunc);
    if (pAddResult != NULL) {
        long result_long = PyLong_AsLong(pAddResult);
        printf("C received from Python: %ld\n", result_long);
        Py_DECREF(pAddResult);
    } else {
        PyErr_Print();
        fprintf(stderr, "Call failed\n");
    }
    Py_DECREF(pModule);
    Py_Finalize();
    return 0;
}

如何编译和运行

确保 my_module.pycall_func.c 在同一目录下。

c语言调用python脚本
(图片来源网络,侵删)
# 编译
gcc call_func.c -o call_func $(python3-config --ldflags)
# 运行
./call_func

预期输出:

C received from Python: Hello, Alice! Welcome to the world of C and Python.
C received from Python: 12

处理更复杂的数据类型(如列表、字典)

Python和C之间的数据转换是关键。Python.h 提供了丰富的宏和函数来处理基本类型和容器类型。

示例:传递列表并获取字典

Python脚本 (data_module.py)

# data_module.py
def process_data(data_list):
    """接收一个列表,返回一个包含统计信息的字典"""
    return {
        "sum": sum(data_list),
        "max": max(data_list),
        "min": min(data_list),
        "length": len(data_list)
    }

C语言程序 (handle_data.c)

#include <Python.h>
#include <stdio.h>
int main() {
    Py_Initialize();
    PyObject *pModule = PyImport_ImportModule("data_module");
    if (!pModule) {
        PyErr_Print();
        return 1;
    }
    PyObject *pFunc = PyObject_GetAttrString(pModule, "process_data");
    if (!pFunc || !PyCallable_Check(pFunc)) {
        PyErr_Print();
        Py_DECREF(pModule);
        return 1;
    }
    // 1. 创建一个Python列表 [10, 20, 30, 40]
    PyObject *pList = PyList_New(4);
    PyList_SetItem(pList, 0, PyLong_FromLong(10));
    PyList_SetItem(pList, 1, PyLong_FromLong(20));
    PyList_SetItem(pList, 2, PyLong_FromLong(30));
    PyList_SetItem(pList, 3, PyLong_FromLong(40));
    // 2. 准备参数
    PyObject *pArgs = PyTuple_New(1);
    PyTuple_SetItem(pArgs, 0, pList); // PyTuple_SetItem steals the reference
    // 3. 调用函数
    PyObject *pResult = PyObject_CallObject(pFunc, pArgs);
    Py_DECREF(pArgs);
    Py_DECREF(pFunc);
    if (pResult && PyDict_Check(pResult)) {
        // 4. 从返回的字典中获取值
        PyObject *pSum = PyDict_GetItemString(pResult, "sum");
        PyObject *pMax = PyDict_GetItemString(pResult, "max");
        if (pSum && pMax) {
            long sum_val = PyLong_AsLong(pSum);
            long max_val = PyLong_AsLong(pMax);
            printf("Sum: %ld, Max: %ld\n", sum_val, max_val);
        }
        Py_DECREF(pResult);
    } else {
        PyErr_Print();
        fprintf(stderr, "Failed to get dictionary result.\n");
    }
    Py_DECREF(pModule);
    Py_Finalize();
    return 0;
}

编译与运行

gcc handle_data.c -o handle_data $(python3-config --ldflags)
./handle_data

预期输出:

Sum: 100, Max: 40

重要注意事项

  1. 内存管理:这是使用Python C API最容易出错的地方。*每一个通过 Py_INCREF 创建或返回的Python对象(`PyObject),在不使用时都必须通过Py_DECREF释放**,忘记释放会导致内存泄漏,像PyTuple_SetItemPyList_SetItem这样的函数会“窃取”引用,所以你不需要再对传入的对象调用Py_DECREF`。
  2. GIL (全局解释器锁):Python的GIL保证了同一时间只有一个线程能执行Python字节码,这意味着,即使在多线程C程序中调用Python,Python代码本身也是串行执行的,如果你的C程序是多线程的,并且需要频繁调用Python,这可能会成为性能瓶颈。
  3. 错误处理:Python函数调用可能会抛出异常,在C中,你需要检查返回的 PyObject* 是否为 NULL,如果是,则用 PyErr_Print() 打印Python的异常堆栈信息。
  4. 跨平台:Windows和Linux/macOS在编译链接时略有不同,Windows通常需要明确指定Python的 includelib 目录,以及 python3x.lib 库文件,Linux/macOS使用 python3-config 工具是最简单的方式。

替代方案:subprocess

如果你的需求只是“执行”一个Python脚本,并且不需要和它进行复杂的交互(比如频繁地交换数据),那么使用C语言的 system()popen() 函数(在Linux/macOS上)或 CreateProcess(在Windows上)来启动一个独立的Python进程会更简单。

示例 (subprocess_example.c)

#include <stdio.h>
#include <stdlib.h>
int main() {
    printf("C program is about to call a Python script.\n");
    // 使用 system() 调用
    // 注意:在Windows上,命令可能是 "python my_script.py"
    int status = system("python3 my_module.py greet Bob");
    if (status == -1) {
        perror("system() call failed");
    } else {
        printf("Python script finished with status: %d\n", status);
    }
    return 0;
}

Python脚本 (my_module.py) - 需要增加命令行参数处理

import sys
def greet(name):
    print(f"Hello, {name}! I'm a Python script called from C.")
if __name__ == "__main__":
    if len(sys.argv) > 1:
        greet(sys.argv[1])
    else:
        print("Usage: python3 my_module.py <name>")

编译与运行

gcc subprocess_example.c -o subprocess_example
./subprocess_example

预期输出

C program is about to call a Python script.
Hello, Bob! I'm a Python script called from C.
Python script finished with status: 0
方法 优点 缺点 适用场景
Python C API 性能高,内存共享,可以深度交互 复杂,手动内存管理,有GIL限制,跨平台编译麻烦 需要高性能、频繁、复杂的数据交换,将Python作为库嵌入到C程序中。
subprocess 简单,无需了解Python C API,天然隔离 性能低,进程创建开销大,数据交换通过文本/文件,交互不便 简单的一次性脚本执行,或者将Python程序作为独立的子进程来运行。

对于大多数需要集成的场景,Python C API 是更强大的选择,但你需要仔细处理其复杂性,对于简单的调用,subprocess 是一个快速且可靠的备选方案。

-- 展开阅读全文 --
头像
验证码错误怎么办?dede提示如何解决?
« 上一篇 03-04
dede中途数据库失败,原因何在?
下一篇 » 03-04
取消
微信二维码
支付宝二维码

目录[+]