1. 以 C 或 C++ 擴充 Python

如果你會撰寫 C 程式語言,那要向 Python 新增內建模組就不困難。這種擴充模組 (extension modules) 可以做兩件在 Python 中無法直接完成的事:它們可以實作新的內建物件型別,並且可以呼叫 C 的函式庫函式和系統呼叫。

為了支援擴充,Python API (Application Programmers Interface) 定義了一組函式、巨集和變數,提供對 Python run-time 系統大部分面向的存取。Python API 是透過引入標頭檔 "Python.h" 來被納入到一個 C 原始碼檔案中。

擴充模組的編譯取決於其預期用途以及你的系統設定;詳細資訊將在後面的章節中提供。

備註

C 擴充介面是 CPython 所特有的,擴充模組在其他 Python 實作上無法運作。在許多情況下,可以避免撰寫 C 擴充並保留對其他實作的可移植性。例如,如果你的用例是呼叫 C 函式庫函式或系統呼叫,你應該考慮使用 ctypes 模組或 cffi 函式庫,而不是編寫自訂的 C 程式碼。這些模組讓你可以撰寫 Python 程式碼來與 C 程式碼介接,而且比起撰寫和編譯 C 擴充模組,這些模組在 Python 實作之間更容易移植。

1.1. 一個簡單範例

讓我們來建立一個叫做 spam(Monty Python 粉絲最愛的食物...)的擴充模組。假設我們要建立一個 Python 介面給 C 函式庫的函式 system() [1] 使用,這個函式接受一個以 null 終止的 (null-terminated) 字元字串做為引數,並回傳一個整數。我們希望這個函式可以在 Python 中被呼叫,如下所示:

>>> import spam
>>> status = spam.system("ls -l")

首先建立一個檔案 spammodule.c。(從過去歷史來看,如果一個模組叫做 spam,包含其實作的 C 檔案就會叫做 spammodule.c;如果模組名稱很長,像是 spammify,模組名稱也可以只是 spammify.c)。

我們檔案的前兩列可以為:

#define PY_SSIZE_T_CLEAN
#include <Python.h>

這會將 Python API 拉進來(你可以加入註解來說明模組的目的,也可以加入版權聲明)。

備註

由於 Python 可能定義一些影響系統上某些標準標頭檔的預處理器定義,你必須在引入任何標準標頭檔之前引入 Python.h

#define PY_SSIZE_T_CLEAN 被用來表示在某些 API 中應該使用 Py_ssize_t 而不是 int。自 Python 3.13 起,它就不再是必要的了,但我們在此保留它以便向後相容。關於這個巨集的描述請參閱字串與緩衝區

所有由 Python.h 定義的使用者可見符號都具有 PyPY 的前綴,在標準標頭檔中定義的除外。

小訣竅

為了向後相容,Python.h 引入了數個標準標頭檔。C 擴充應該引入它們使用的標準標頭檔,而不應依賴這些隱式的引入。如果使用限定 C API 版本 3.13 或更新版本,隱式引入的有:

  • <assert.h>

  • <intrin.h>(在 Windows 上)

  • <inttypes.h>

  • <limits.h>

  • <math.h>

  • <stdarg.h>

  • <wchar.h>

  • <sys/types.h>(如果存在)

如果 Py_LIMITED_API 未被定義,或是被設定為版本 3.12 或更舊,則也會引入以下標頭檔:

  • <ctype.h>

  • <unistd.h>(在 POSIX 上)

如果 Py_LIMITED_API 未被定義,或是被設定為版本 3.10 或更舊,則也會引入以下標頭檔:

  • <errno.h>

  • <stdio.h>

  • <stdlib.h>

  • <string.h>

接下來我們要加入到模組檔案的是 C 函式,當 Python 運算式 spam.system(string) 要被求值 (evaluated) 時就會被呼叫(我們很快就會看到它最後是如何被呼叫的):

static PyObject *
spam_system(PyObject *self, PyObject *args)
{
    const char *command;
    int sts;

    if (!PyArg_ParseTuple(args, "s", &command))
        return NULL;
    sts = system(command);
    return PyLong_FromLong(sts);
}

可以很直觀地從 Python 的引數串列(例如單一的運算式 "ls -l")直接轉換成傳給 C 函式的引數。C 函式總是有兩個引數,習慣上會命名為 selfargs

對於模組層級的函式,self 引數會指向模組物件;而對於方法來說則是指向物件的實例。

args 引數會是一個指向包含引數的 Python 元組物件的指標。元組中的每一項都對應於呼叫的引數串列中的一個引數。引數是 Python 物件 --- 為了在我們的 C 函式中對它們做任何事情,我們必須先將它們轉換成 C 值。Python API 中的 PyArg_ParseTuple() 函式能夠檢查引數型別並將他們轉換為 C 值。它使用模板字串來決定所需的引數型別以及儲存轉換值的 C 變數型別。稍後會再詳細說明。

如果所有的引數都有正確的型別,且其元件已儲存在傳入位址的變數中,則 PyArg_ParseTuple() 會回傳 true(非零)。如果傳入的是無效引數串列則回傳 false(零)。在後者情況下,它也會產生適當的例外,因此呼叫函式可以立即回傳 NULL(就像我們在範例中所看到的)。