1. 在其它 App 內嵌入 Python

前面的章節討論了如何擴充 Python,也就是如何透過附加一個 C 函式庫來擴充 Python 的功能。但也可以反過來做:將 Python 嵌入你的 C/C++ 應用程式中。嵌入讓你的應用程式能夠以 Python 而非 C 或 C++ 來實作應用程式的某些功能。這可以用於許多目的;其中一個例子是允許使用者透過撰寫一些 Python 腳本來根據他們的需求客製化應用程式。如果某些功能用 Python 寫起來比較容易,你也可以自己使用這種方法。

嵌入 Python 與擴充 Python 類似,但不完全相同。差別在於當你擴充 Python 時,應用程式的主程式仍然是 Python 直譯器,而當你嵌入 Python,主程式可能與 Python 無關 — 相反地,應用程式的某些部分偶爾會呼叫 Python 直譯器來執行一些 Python 程式碼。

所以如果你要嵌入 Python,你要提供自己的主程式。這個主程式必須做的事情之一是初始化 Python 直譯器,或至少必須要呼叫函式 Py_Initialize()。還有一些可選的呼叫來傳遞命令列引數給 Python。然後你就可以在應用程式的任何部分呼叫直譯器。

有幾種不同的方式來呼叫直譯器:你可以傳遞一個包含 Python 陳述式的字串給 PyRun_SimpleString(),或者你可以傳遞一個 stdio 檔案指標和檔案名稱(僅用於錯誤訊息中的識別)給 PyRun_SimpleFile()。你也可以呼叫前面章節中描述的較低層級操作來建構和使用 Python 物件。

也參考

Python/C API 參考手冊

Python 的 C 介面詳細資訊在此手冊中提供。大量必要的資訊可以在這裡找到。

1.1. 非常高階的嵌入

嵌入 Python 最簡單的形式是使用非常高階的介面。此介面用於執行 Python 腳本而無需直接與應用程式互動。例如這可以用來對檔案執行一些操作。

#define PY_SSIZE_T_CLEAN
#include <Python.h>

int
main(int argc, char *argv[])
{
    PyStatus status;
    PyConfig config;
    PyConfig_InitPythonConfig(&config);

    /* 建議但非必要 */
    status = PyConfig_SetBytesString(&config, &config.program_name, argv[0]);
    if (PyStatus_Exception(status)) {
        goto exception;
    }

    status = Py_InitializeFromConfig(&config);
    if (PyStatus_Exception(status)) {
        goto exception;
    }
    PyConfig_Clear(&config);

    PyRun_SimpleString("from time import time,ctime\n"
                       "print('Today is', ctime(time()))\n");
    if (Py_FinalizeEx() < 0) {
        exit(120);
    }
    return 0;

  exception:
     PyConfig_Clear(&config);
     Py_ExitStatusException(status);
}

備註

#define PY_SSIZE_T_CLEAN 被用來指示某些 API 應該使用 Py_ssize_t 而不是 int。從 Python 3.13 開始不再需要它,但我們保留它以維持向後相容性。關於此巨集的描述請參閱 字串與緩衝區

PyConfig.program_name 的設定應該在 Py_InitializeFromConfig() 前呼叫,以告知直譯器 Python run-time 函式庫的路徑。接下來,Python 直譯器會用 Py_Initialize() 初始化,然後執行一個硬編碼的 Python 腳本來印出日期和時間。之後,Py_FinalizeEx() 呼叫會關閉直譯器,接著程式結束。在真實的程式中,你可能想要從另一個來源取得 Python 腳本,或許是文字編輯器例程、檔案或資料庫。從檔案取得 Python 程式碼可以更好地使用 PyRun_SimpleFile() 函式來完成,這樣可以省去分配記憶體空間和載入檔案內容的麻煩。

1.2. 超越非常高階嵌入:概觀

高階介面讓你能夠從應用程式中執行任意的 Python 程式碼片段,但交換資料值的過程可以說相當繁瑣。如果你想進行這類操作,應該使用較低階的呼叫。雖然需要撰寫更多的 C 程式碼,但幾乎可以實現任何功能。

需要注意的是,雖然目的不同,但擴充 Python 與嵌入 Python 其實是非常相似的操作,前面章節討論的大多數主題在這裡同樣適用。為了說明這一點,請思考從 Python 到 C 的擴充程式碼實際上做了什麼:

  1. 將資料值從 Python 轉換為 C,

  2. 使用轉換後的值呼叫 C 例程,並

  3. 將呼叫中的資料值從 C 轉換為 Python。

當嵌入 Python 時,介面程式碼會:

  1. 將資料值從 C 轉換為 Python,

  2. 使用轉換後的值呼叫 Python 介面例程,並

  3. 將呼叫中的資料值從 Python 轉換為 C。

如你所見,資料轉換的步驟只是互換了順序,以配合跨語言傳遞方向的不同。唯一的差別在於兩個資料轉換之間所呼叫的例程:在擴充時你呼叫的是 C 例程;在嵌入時則呼叫 Python 例程。

本章不會討論如何將資料從 Python 轉換為 C 或從 C 轉換回 Python,且假設讀者已經知道參照的正確使用方式與錯誤處理。由於這些部分與擴充直譯器時相同,相關資訊可參考前面的章節。

1.3. 純嵌入

第一個程式的目標是執行 Python 腳本中的函式。就像在非常高階介面的章節中一樣,Python 直譯器不會直接與應用程式互動(但這在下一節會改變)。

執行 Python 腳本中定義函式的程式碼是:

#define PY_SSIZE_T_CLEAN
#include <Python.h>

int
main(int argc, char *argv[])
{
    PyObject *pName, *pModule, *pFunc;
    PyObject *pArgs, *pValue;
    int i;

    if (argc < 3) {
        fprintf(stderr,"Usage: call pythonfile funcname [args]\n");
        return 1;
    }

    Py_Initialize();
    pName = PyUnicode_DecodeFSDefault(argv[1]);
    /* Error checking of pName left out */

    pModule = PyImport_Import(pName);
    Py_DECREF(pName);

    if (pModule != NULL) {
        pFunc = PyObject_GetAttrString(pModule, argv[2]);
        /* pFunc is a new reference */

        if (pFunc && PyCallable_Check(pFunc)) {
            pArgs = PyTuple_New(argc - 3);
            for (i = 0; i < argc - 3; ++i) {
                pValue = PyLong_FromLong(atoi(argv[i + 3]));
                if (!pValue) {
                    Py_DECREF(pArgs);
                    Py_DECREF(pModule);
                    fprintf(stderr, "Cannot convert argument\n");
                    return 1;
                }
                /* pValue reference stolen here: */
                PyTuple_SetItem(pArgs, i, pValue);
            }
            pValue = PyObject_CallObject(pFunc, pArgs);
            Py_DECREF(pArgs);
            if (pValue != NULL) {
                printf("Result of call: %ld\n", PyLong_AsLong(pValue));
                Py_DECREF(pValue);
            }
            else {
                Py_DECREF(pFunc);
                Py_DECREF(pModule);
                PyErr_Print();
                fprintf(stderr,"Call failed\n");
                return 1;
            }
        }
        else {
            if (PyErr_Occurred())
                PyErr_Print();
            fprintf(stderr, "Cannot find function \"%s\"\n", argv[2]);
        }
        Py_XDECREF(pFunc);
        Py_DECREF(pModule);
    }
    else {
        PyErr_Print();
        fprintf(stderr, "Failed to load \"%s\"\n", argv[1]);
        return 1;
    }
    if (Py_FinalizeEx() < 0) {
        return 120;
    }
    return 0;
}

此程式碼使用 argv[1] 載入 Python 腳本,並呼叫 argv[2] 中所指定的函式。其整數引數則來自 argv 陣列中的其他值。如果你編譯並連結此程式(我們稱完成的可執行檔為 call),並用它來執行 Python 腳本,例如:

def multiply(a,b):