首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> C++ >> C++ 入門

C++ 嵌入 Python 完整實戰指南:從初始化、模組載入到函數呼叫與記憶體管理

     編輯:C++ 入門

C++ 高效能與 Python 靈活腳本能力的結合,是眾多高效能應用(遊戲邏輯、科學計算、自動化測試、AI 推理預處理)的首選架構。本文提供完整的 C/C++ 嵌入 Python 實作範例,涵蓋解譯器初始化、模組載入、函數呼叫、引數傳遞、回傳值轉換與參考計數管理,助你快速構建穩健的雙語言系統。

一、核心 API 流程總覽

  1. Py_Initialize():初始化 Python 解譯器(建立 GIL、載入內建模組)。
  2. PyImport_ImportModule() / PyImport_Import():載入 Python 模組(.py/.pyc/.so/.pyd)。
  3. PyObject_GetAttrString() / PyDict_GetItemString():取得模組屬性(函數/類別/變數)。
  4. PyTuple_New() + PyTuple_SetItem() / Py_BuildValue():建構呼叫引數元組。
  5. PyObject_CallObject() / PyObject_Call():呼叫可呼叫物件。
  6. PyLong_AsLong() / PyFloat_AsDouble() / PyUnicode_AsUTF8():回傳值轉換為 C 型別。
  7. Py_DECREF() / Py_XDECREF():釋放 Python 物件參考(極關鍵,防記憶體洩漏)。
  8. Py_FinalizeEx():關閉解譯器(釋放所有資源)。

二、完整範例:呼叫 Python 腳本 test1.py 中的 Add(a, b) 函數

Python 腳本 (test1.py)


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

C++ 嵌入程式碼


#include 
#include 

int main() {
    // 1. 初始化解譯器
    Py_Initialize();
    if (!Py_IsInitialized()) {
        std::cerr << "Python 初始化失敗\n";
        return -1;
    }

    // 2. 載入模組 (建議使用 PyImport_ImportModule,直接接受 UTF-8 字串)
    PyObject* pModule = PyImport_ImportModule("test1");
    if (!pModule) {
        PyErr_Print(); // 列印 Python 異常堆疊
        std::cerr << "無法載入模組 test1\n";
        Py_FinalizeEx();
        return -1;
    }
    std::cout << "成功載入模組 test1\n";

    // 3. 取得函數物件
    PyObject* pFunc = PyObject_GetAttrString(pModule, "Add");
    if (!pFunc || !PyCallable_Check(pFunc)) {
        std::cerr << "找不到可呼叫的 Add 函數\n";
        Py_XDECREF(pFunc);
        Py_DECREF(pModule);
        Py_FinalizeEx();
        return -1;
    }

    // 4. 準備引數: Add(10, 20)
    // 方法 A: 手動建構 Tuple (教學用)
    PyObject* pArgs = PyTuple_New(2);
    PyTuple_SetItem(pArgs, 0, PyLong_FromLong(10)); // 偷取參考,無需 DECREF
    PyTuple_SetItem(pArgs, 1, PyLong_FromLong(20));

    // 方法 B: 使用 Py_BuildValue 簡化 (推薦實務用)
    // PyObject* pArgs = Py_BuildValue("(ii)", 10, 20); // "(ii)" 表兩個 int 組成 tuple

    // 5. 呼叫函數
    PyObject* pResult = PyObject_CallObject(pFunc, pArgs);
    if (!pResult) {
        PyErr_Print();
        std::cerr << "函數呼叫失敗\n";
    } else {
        // 6. 轉換回傳值
        long result = PyLong_AsLong(pResult);
        if (result == -1 && PyErr_Occurred()) {
            PyErr_Print();
        } else {
            std::cout << "Add(10, 20) = " << result << "\n"; // 輸出 30
        }
        Py_DECREF(pResult);
    }

    // 7. 釋放資源 (逆序,嚴格配對)
    Py_DECREF(pArgs);
    Py_DECREF(pFunc);
    Py_DECREF(pModule);

    // 8. 關閉解譯器
    Py_FinalizeEx();
    return 0;
}

三、關鍵技術細節深度解析

1. 參考計數管理:嵌入 Python 最大坑點

  • 新參考:PyImport_ImportModule、PyObject_GetAttrString、PyTuple_New、PyLong_FromLong、Py_BuildValue、PyObject_CallObject 回傳「新參考」,呼叫者負責 Py_DECREF。
  • 偷取參考:PyTuple_SetItem、PyList_SetItem、PyDict_SetItem 「偷取」傳入物件參考,呼叫者不可再 DECREF。
  • 借用參考:PyTuple_GetItem、PyList_GetItem、PyDict_GetItem 回傳「借用參考」,呼叫者不可 DECREF,若需持有需自行 Py_INCREF。
  • Py_XDECREF:安全版 DECREF,接受 nullptr,適用於可能為空的指標。

2. 錯誤處理模式


PyObject* pResult = PyObject_CallObject(pFunc, pArgs);
if (!pResult) {
    // Python 例外已設定,需檢查並處理
    if (PyErr_ExceptionMatches(PyExc_TypeError)) {
        // 特定例外處理
    }
    PyErr_Print(); // 列印預設 traceback 到 stderr
    PyErr_Clear(); // 清除例外狀態 (若已處理)
}

3. GIL (Global Interpreter Lock) 與多執行緒

  • 嵌入多執行緒 C++ 應用時,每個呼叫 Python API 的執行緒必須持有 GIL。
  • 模式:PyGILState_STATE state = PyGILState_Ensure(); ...呼叫 Python API... PyGILState_Release(state);。
  • 長時間 C++ 計算應釋放 GIL:Py_BEGIN_ALLOW_THREADS ...計算... Py_END_ALLOW_THREADS。

4. Python 3 字串處理

  • Python 3 內部使用 Unicode,C API 主要接受/回傳 UTF-8 const char*。
  • 建構字串:PyUnicode_FromString("中文") 或 PyUnicode_FromStringAndSize()。
  • 提取字串:const char* utf8 = PyUnicode_AsUTF8(pUnicodeObj); (回傳借用指標,物件存活期間有效)。

四、進階主題與最佳實踐

RAII 封裝 Python 物件 (強烈推薦)


class PyObjectPtr {
    PyObject* ptr_;
public:
    PyObjectPtr(PyObject* p = nullptr) : ptr_(p) {}
    ~PyObjectPtr() { Py_XDECREF(ptr_); }
    PyObjectPtr(const PyObjectPtr&) = delete;
    PyObjectPtr& operator=(const PyObjectPtr&) = delete;
    PyObjectPtr(PyObjectPtr&& other) noexcept : ptr_(other.ptr_) { other.ptr_ = nullptr; }
    PyObjectPtr& operator=(PyObjectPtr&& other) noexcept {
        Py_XDECREF(ptr_); ptr_ = other.ptr_; other.ptr_ = nullptr; return *this;
    }
    PyObject* get() const { return ptr_; }
    PyObject* release() { PyObject* p = ptr_; ptr_ = nullptr; return p; }
    operator bool() const { return ptr_ != nullptr; }
};

// 使用範例
PyObjectPtr pModule(PyImport_ImportModule("test1"));
PyObjectPtr pFunc(PyObject_GetAttrString(pModule.get(), "Add"));
PyObjectPtr pArgs(Py_BuildValue("(ii)", 10, 20));
PyObjectPtr pResult(PyObject_CallObject(pFunc.get(), pArgs.get()));
// 離開作用域自動釋放,零洩漏

pybind11:現代 C++ 綁定庫的最佳選擇

手寫 Python C API 繁瑣且易錯。pybind11 提供純標頭檔、C++11 風格綁定,自動處理參考計數、型別轉換、例外傳播、GIL 管理。


// pybind11 綁定範例 (編譯為 .so/.pyd 供 Python import)
#include 
namespace py = pybind11;

int add(int a, int b) { return a + b; }

PYBIND11_MODULE(my_module, m) {
    m.def("add", &add, "Add two integers");
}

反向嵌入(C++ 執行 Python 程式碼)pybind11 亦支援 py::exec()、py::module::import() 等高層 API,生產環境強烈建議採用。

五、編譯與連結關鍵點

  • 包含目錄:$(Python_INCLUDE_DIRS) (如 /usr/include/python3.11 或 C:\Python311\include)
  • 連結庫:python3.11 (Linux/macOS) 或 python311.lib (Windows)
  • CMake 現代寫法:find_package(Python3 REQUIRED COMPONENTS Interpreter Development) → target_link_libraries(myapp PRIVATE Python3::Python)

六、總結

C++ 嵌入 Python 核心在於:正確的參考計數管理、完善的異常處理、GIL 感知的多執行緒設計。手寫 C API 適合學習與極簡依賴場景,實務專案請直接採用 pybind11 或 nanobind,以現代 C++ 風格享受零成本抽象的綁定體驗。

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