首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 綜合教學

C/C++ 與 Python 混合編程完全指南:嵌入式調用與擴展開發

     編輯:Java 綜合教學

前言

Python 擁有豐富的科學計算、數據分析、機器學習生態,而 C/C++ 擁有極致性能與系統級能力。通過 C 調用 Python(嵌入式) 與 Python 調用 C/C++(擴展模塊) 兩種模式,可實現「性能核心用 C,業務邏輯用 Python」的最佳工程實踐。本文系統講解嵌入式調用全流程、核心 API、數據轉換與工程化注意事項。

一、環境準備與基礎配置

組件版本/路徑說明
作業系統Ubuntu 14.04 / Linux生產環境推薦 CentOS 7+ / Ubuntu 18.04+
編譯器GCC 4.8.2+需支持 C99/C++11
Python2.7.6 / 3.x新項目強烈建議 Python 3.8+
頭文件Python.h通常在 /usr/include/pythonX.Y/
庫文件libpythonX.Y.so鏈接時加 -lpythonX.Y

編譯命令範例

# C 調用 Python 2.7
gcc -o c_call_python c_call_python.c -I/usr/include/python2.7 -lpython2.7

# C++ 調用 Python 3.8
g++ -o cpp_call_python cpp_call_python.cpp -I/usr/include/python3.8 -lpython3.8 -std=c++11

二、C 調用 Python 核心流程(嵌入式編程)

2.1 標準調用步驟

  1. 初始化:Py_Initialize(),檢查 Py_IsInitialized()
  2. 設置模塊搜索路徑:PyRun_SimpleString("import sys; sys.path.append('./')")
  3. 導入模塊:PyImport_ImportModule("module_name")(模塊名非文件名)
  4. 獲取函數對象:PyObject_GetAttrString(pModule, "func_name")
  5. 構造參數元組:PyTuple_New(n) + PyTuple_SetItem 或 Py_BuildValue
  6. 調用函數:PyObject_CallObject(pFunc, pArgs)
  7. 解析返回值:PyLong_AsLong、PyFloat_AsDouble、PyUnicode_AsUTF8 等
  8. 引用計數管理:Py_DECREF 釋放所有 PyObject*
  9. 清理:Py_Finalize()

2.2 完整代碼示例(Python 3.x 兼容)

#include 
#include 
#include 

int main(int argc, char** argv) {
    if (argc != 3) {
        printf("Usage: %s  \n", argv[0]);
        return -1;
    }
    long a = strtol(argv[1], NULL, 10);
    long b = strtol(argv[2], NULL, 10);

    Py_Initialize();
    if (!Py_IsInitialized()) {
        fprintf(stderr, "Python 初始化失敗\n");
        return -1;
    }

    // 添加當前目錄到模塊搜索路徑
    PyRun_SimpleString("import sys");
    PyRun_SimpleString("sys.path.append('.')");

    // 導入模塊 math_test (對應 math_test.py)
    PyObject* pModule = PyImport_ImportModule("math_test");
    if (!pModule) {
        PyErr_Print();
        fprintf(stderr, "導入模塊失敗\n");
        Py_Finalize();
        return -1;
    }

    // 獲取 add_func 函數
    PyObject* pFunc = PyObject_GetAttrString(pModule, "add_func");
    if (!pFunc || !PyCallable_Check(pFunc)) {
        PyErr_Print();
        fprintf(stderr, "獲取函數失敗\n");
        Py_DECREF(pModule);
        Py_Finalize();
        return -1;
    }

    // 構造參數: (a, b)
    PyObject* pArgs = Py_BuildValue("(ll)", a, b);  // 直接構造元組
    if (!pArgs) {
        fprintf(stderr, "構造參數失敗\n");
        Py_DECREF(pFunc);
        Py_DECREF(pModule);
        Py_Finalize();
        return -1;
    }

    // 調用
    PyObject* pResult = PyObject_CallObject(pFunc, pArgs);
    Py_DECREF(pArgs);
    if (!pResult) {
        PyErr_Print();
        fprintf(stderr, "函數調用失敗\n");
        Py_DECREF(pFunc);
        Py_DECREF(pModule);
        Py_Finalize();
        return -1;
    }

    // 解析結果 (Python 3 用 PyLong_AsLong)
    long result = PyLong_AsLong(pResult);
    printf("%ld + %ld = %ld\n", a, b, result);

    // 清理
    Py_DECREF(pResult);
    Py_DECREF(pFunc);
    Py_DECREF(pModule);
    Py_Finalize();
    return 0;
}

2.3 對應的 Python 模塊 (math_test.py)

def add_func(a, b):
    return a + b

def sub_func(a, b):
    return a - b

if __name__ == "__main__":
    # 可獨立運行測試
    print(add_func(12, 15))

三、核心 API 深度解析:數據轉換格式字符串

Py_BuildValue(C→Python)與 PyArg_ParseTuple(Python→C)使用相同格式字符串:

格式符C 類型Python 類型說明
iintint有符號整數
llongint長整數
ddoublefloat雙精度浮點
ffloatfloat單精度浮點
schar*strUTF-8 字符串
s#char*, intstr指定長度字符串
ychar*bytes字節串
OPyObject*任意傳遞現有對象(借用引用)
NPyObject*任意傳遞現有對象(偷取引用)
(...)—tuple構造元組
[...]—list構造列表
{...}—dict構造字典

常用構造示例

// 無參
Py_BuildValue("")                    // None
// 單值
Py_BuildValue("i", 42)               // 42
Py_BuildValue("s", "hello")          // 'hello'
// 元組
Py_BuildValue("(ii)", 1, 2)          // (1, 2)
Py_BuildValue("iii", 1, 2, 3)        // (1, 2, 3) 多格式自動成元組
// 列表
Py_BuildValue("[ii]", 1, 2)          // [1, 2]
// 字典
Py_BuildValue("{s:i, s:i}", "a", 1, "b", 2)  // {'a':1, 'b':2}

四、C++ 調用 Python:面向對象封裝與異常處理

4.1 RAII 封裝 Python 對象智能指針

#include 
#include 
#include 
#include 

// 自定義 deleter
struct PyObjectDeleter {
    void operator()(PyObject* obj) const {
        if (obj) Py_DECREF(obj);
    }
};

// 智能指針別名
using PyObjPtr = std::unique_ptr;

// 封裝調用器
class PythonCaller {
public:
    PythonCaller() {
        Py_Initialize();
        if (!Py_IsInitialized()) throw std::runtime_error("Python 初始化失敗");
        PyRun_SimpleString("import sys");
        PyRun_SimpleString("sys.path.append('.')");
    }
    
    ~PythonCaller() { Py_Finalize(); }
    
    // 禁用拷貝,啟用移動
    PythonCaller(const PythonCaller&) = delete;
    PythonCaller& operator=(const PythonCaller&) = delete;
    PythonCaller(PythonCaller&&) = default;
    PythonCaller& operator=(PythonCaller&&) = default;

    template
    long callInt(const char* module, const char* func, Args... args) {
        PyObjPtr pModule(PyImport_ImportModule(module));
        if (!pModule) throwPythonError("導入模塊失敗: " + std::string(module));
        
        PyObjPtr pFunc(PyObject_GetAttrString(pModule.get(), func));
        if (!pFunc || !PyCallable_Check(pFunc.get())) 
            throwPythonError("獲取函數失敗: " + std::string(func));
        
        // 構造參數元組 (C++17 折疊表達式)
        PyObjPtr pArgs(Py_BuildValue("(" + std::string(sizeof...(Args), 'l') + ")", args...));
        if (!pArgs) throwPythonError("構造參數失敗");
        
        PyObjPtr pResult(PyObject_CallObject(pFunc.get(), pArgs.get()));
        if (!pResult) throwPythonError("函數調用異常");
        
        return PyLong_AsLong(pResult.get());
    }

private:
    void throwPythonError(const std::string& msg) {
        PyErr_Print();
        throw std::runtime_error(msg);
    }
};

// 使用示例
int main() {
    try {
        PythonCaller py;
        long result = py.callInt("math_test", "add_func", 12L, 15L);
        printf("Result: %ld\n", result);  // 27
    } catch (const std::exception& e) {
        fprintf(stderr, "Error: %s\n", e.what());
        return -1;
    }
    return 0;
}

五、工程化關鍵注意事項

5.1 線程安全與 GIL

  • 嵌入式調用默認持有 GIL,單線程安全
  • 多線程場景需手動管理:PyGILState_Ensure() / PyGILState_Release()
  • 長時間 C 計算可釋放 GIL:Py_BEGIN_ALLOW_THREADS / Py_END_ALLOW_THREADS

5.2 異常傳播與錯誤處理

  • 所有 Python C API 失敗返回 NULL/ -1,需檢查 PyErr_Occurred()
  • 使用 PyErr_Print() 輸出堆棧,或 PyErr_Fetch() 獲取異常對象自定義處理
  • C++ 端建議封裝為異常拋出,統一錯誤處理入口

5.3 內存管理鐵律

  1. 新引用:Py_BuildValue、PyImport_ImportModule、PyObject_CallObject 返回新引用,必須 Py_DECREF
  2. 借用引用:PyTuple_GetItem、PyList_GetItem、PyDict_GetItem 不增加引用計數,不可 DECREF
  3. 偷取引用:PyTuple_SetItem、PyList_SetItem、PyDict_SetItem 偷取參數引用,調用後不可再 DECREF 參數
  4. 使用 RAII 智能指針自動化管理,杜絕泄漏

5.4 路徑與編碼問題

  • 模塊路徑:優先用絕對路徑 sys.path.insert(0, '/abs/path')
  • 字符串編碼:Python 3 強制 Unicode,C 端傳 UTF-8,用 PyUnicode_FromString/PyUnicode_AsUTF8
  • 文件編碼:Python 源碼保存為 UTF-8,首行加 # -*- coding: utf-8 -*-

六、替代方案對比:何時選擇哪種模式

方案適用場景優點缺點學習曲線
C 調用 Python (嵌入式) 宿主程序是 C/C++,需動態腳本化、插件系統、調用 AI 模型 無需編譯 Python 擴展,腳本熱更新,開發靈活 啟動開銷大,GIL 限制併發,調試困難 中
Python 調用 C (擴展模塊) 性能熱點、硬件驅動、底層庫封裝 原生性能,無 GIL 限制(釋放後),Python 風格調用 需編寫 C 擴展代碼,編譯部署複雜,內存管理易出錯 高
ctypes / cffi 快速調用現有 .so/.dll,無需寫 C 擴展代碼 純 Python 實現,無編譯步驟,跨平台 性能略低,複雜數據結構映射繁瑣 低
Cython Python 代碼加速、編寫擴展模塊 語法接近 Python,自動生成 C 代碼,性能接近原生 需編譯工具鏈,調試相對困難 中
pybind11 (C++11+) 現代 C++ 綁定 Python,頭文件-only 語法優雅,自動類型轉換,支持 C++17 特性 需 C++11 編譯器,模板編譯慢 中低

七、總結與最佳實踐建議

  1. 新項目首選 Python 3.8+,放棄 Python 2.7 歷史包袱
  2. 嵌入式場景:封裝 RAII 管理器,統一異常處理,嚴格管理引用計數
  3. 性能熱點:優先考慮 pybind11 編寫 C++ 擴展,或 Cython 加速核心邏輯
  4. 避免在高頻路徑反復初始化/終結 Python,進程生命週期內僅初始化一次
  5. 多線程併發:C++ 端釋放 GIL 執行計算密集任務,Python 端用 multiprocessing 繞過 GIL
  6. 構建系統:用 CMake + find_package(Python3 REQUIRED) 管理編譯鏈接,配合 pybind11 的 pybind11_add_module

混合編程是系統工程能力的體現,核心在於邊界劃分清晰、數據轉換零拷貝、生命週期精確可控。掌握本文核心 API 與工程化模式,即可在性能與生產力間游刃有餘。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved