引言¶
Python 的應用程式程式設計介面 (API) 允許 C 和 C++ 程式設計師在不同層面訪問 Python 直譯器。該 API 同樣適用於 C++,但為簡潔起見,通常將其稱為 Python/C API。使用 Python/C API 有兩個根本不同的原因。第一個原因是為了編寫特定用途的擴充套件模組;這些是擴充套件 Python 直譯器的 C 模組。這可能是最常見的用途。第二個原因是將 Python 作為更大應用程式的元件;這種技術通常被稱為在應用程式中嵌入 Python。
編寫擴充套件模組是一個相對成熟的過程,其中“食譜”方法效果很好。有幾種工具可以在一定程度上自動化這個過程。雖然人們從 Python 早期就將其嵌入到其他應用程式中,但嵌入 Python 的過程不如編寫擴充套件模組那麼直接。
許多 API 函式無論您是嵌入還是擴充套件 Python 都很有用;此外,大多數嵌入 Python 的應用程式也需要提供自定義擴充套件,因此在嘗試將 Python 嵌入到實際應用程式中之前,熟悉如何編寫擴充套件模組可能是一個好主意。
語言版本相容性¶
Python 的 C API 與 C11 和 C++11 版本的 C 和 C++ 相容。
這是一個下限:C API 不要求使用更高版本的 C/C++ 功能。您不需要啟用編譯器的“c11 模式”。
編碼標準¶
如果您正在編寫要包含在 CPython 中的 C 程式碼,則必須遵循 PEP 7 中定義的指南和標準。這些指南適用於您貢獻的 Python 版本。遵循這些約定對於您自己的第三方擴充套件模組來說不是必需的,除非您最終希望將它們貢獻給 Python。
標頭檔案¶
使用 Python/C API 所需的所有函式、型別和宏定義都透過以下行包含在您的程式碼中
#define PY_SSIZE_T_CLEAN
#include <Python.h>
這意味著包含了以下標準標頭檔案:<stdio.h>
、<string.h>
、<errno.h>
、<limits.h>
、<assert.h>
和 <stdlib.h>
(如果可用)。
備註
由於 Python 可能會定義一些預處理器定義,這些定義會影響某些系統上的標準標頭檔案,因此您必須在包含任何標準標頭檔案之前包含 Python.h
。
建議在包含 Python.h
之前始終定義 PY_SSIZE_T_CLEAN
。有關此宏的描述,請參閱解析引數和構建值。
Python.h 定義的所有使用者可見名稱(除了包含的標準標頭檔案定義的名稱)都帶有字首 Py
或 _Py
。以 _Py
開頭的名稱供 Python 實現內部使用,擴充套件作者不應使用。結構成員名稱沒有保留字首。
備註
使用者程式碼不應定義以 Py
或 _Py
開頭的名稱。這會混淆讀者,並危及使用者程式碼向未來 Python 版本的可移植性,因為未來版本可能會定義更多以這些字首開頭的名稱。
標頭檔案通常與 Python 一起安裝。在 Unix 上,它們位於目錄 prefix/include/pythonversion/
和 exec_prefix/include/pythonversion/
中,其中 prefix
和 exec_prefix
由 Python 的 configure 指令碼的相應引數定義,而 version 是 '%d.%d' % sys.version_info[:2]
。在 Windows 上,標頭檔案安裝在 prefix/include
中,其中 prefix
是安裝程式指定的安裝目錄。
要包含標頭檔案,請將這兩個目錄(如果不同)都放在編譯器的包含搜尋路徑上。請不要將父目錄放在搜尋路徑上,然後使用 #include <pythonX.Y/Python.h>
;這在多平臺構建上會中斷,因為 prefix
下的平臺無關標頭檔案包含來自 exec_prefix
的平臺特定標頭檔案。
C++ 使用者應注意,儘管 API 完全使用 C 定義,但標頭檔案正確地將入口點宣告為 extern "C"
。因此,從 C++ 使用 API 不需要做任何特殊的事情。
有用宏¶
Python 標頭檔案中定義了幾個有用的宏。許多宏的定義更靠近它們有用的地方(例如,Py_RETURN_NONE
、PyMODINIT_FUNC
)。其他更通用的實用宏在此處定義。這不一定是一個完整的列表。
-
Py_ABS(x)¶
返回
x
的絕對值。在 3.3 版本加入。
-
Py_ALWAYS_INLINE¶
請求編譯器始終內聯靜態行內函數。編譯器可以忽略它並決定不內聯該函式。
當在停用函式內聯的除錯模式下構建 Python 時,它可用於內聯效能關鍵的靜態行內函數。例如,MSC 在除錯模式下構建時會停用函式內聯。
盲目地用 Py_ALWAYS_INLINE 標記靜態行內函數可能會導致效能下降(例如由於程式碼大小增加)。編譯器通常比開發人員更擅長成本/收益分析。
如果 Python 是在除錯模式下構建的(如果定義了
Py_DEBUG
宏),則Py_ALWAYS_INLINE
宏不起作用。它必須在函式返回型別之前指定。用法
static inline Py_ALWAYS_INLINE int random(void) { return 4; }
在 3.11 版本中新增。
-
Py_CHARMASK(c)¶
引數必須是一個字元或一個在 [-128, 127] 或 [0, 255] 範圍內的整數。此宏返回轉換為
unsigned char
的c
。
-
Py_DEPRECATED(version)¶
用於已棄用的宣告。宏必須放置在符號名稱之前。
示例
Py_DEPRECATED(3.8) PyAPI_FUNC(int) Py_OldFunction(void);
版本 3.8 中的變化: 添加了 MSVC 支援。
-
Py_GETENV(s)¶
類似於
getenv(s)
,但如果在命令列上傳遞了-E
則返回NULL
(參閱PyConfig.use_environment
)。
-
Py_MAX(x, y)¶
返回
x
和y
之間的最大值。在 3.3 版本加入。
-
Py_MEMBER_SIZE(type, member)¶
以位元組為單位返回結構體 (
type
)member
的大小。在 3.6 版本加入。
-
Py_MIN(x, y)¶
返回
x
和y
之間的最小值。在 3.3 版本加入。
-
Py_NO_INLINE¶
停用函式內聯。例如,它減少了 C 棧消耗:在大量內聯程式碼的 LTO+PGO 構建中很有用(參閱 bpo-33720)。
用法
Py_NO_INLINE static int random(void) { return 4; }
在 3.11 版本中新增。
-
Py_STRINGIFY(x)¶
將
x
轉換為 C 字串。例如,Py_STRINGIFY(123)
返回"123"
。在 3.4 版本加入。
-
Py_UNREACHABLE()¶
當您有一個按設計無法訪問的程式碼路徑時,請使用此宏。例如,在
switch
語句的default:
子句中,所有可能的值都在case
語句中涵蓋。在您可能想放置assert(0)
或abort()
呼叫的地方使用此宏。在釋出模式下,此宏有助於編譯器最佳化程式碼,並避免關於不可達程式碼的警告。例如,在釋出模式下,GCC 上的宏是透過
__builtin_unreachable()
實現的。Py_UNREACHABLE()
的一個用途是跟在一個永遠不會返回但未宣告為_Py_NO_RETURN
的函式的呼叫之後。如果程式碼路徑不太可能但可能在異常情況下達到,則不得使用此宏。例如,在記憶體不足的情況下或系統呼叫返回超出預期範圍的值。在這種情況下,最好向呼叫者報告錯誤。如果無法向呼叫者報告錯誤,可以使用
Py_FatalError()
。在 3.7 版本加入。
-
Py_UNUSED(arg)¶
在函式定義中,使用此宏來消除未使用的引數的編譯器警告。示例:
int func(int a, int Py_UNUSED(b)) { return a; }
。在 3.4 版本加入。
-
PyDoc_STRVAR(name, str)¶
建立一個名為
name
的變數,可在文件字串中使用。如果 Python 在沒有文件字串的情況下構建,則該值將為空。使用
PyDoc_STRVAR
作為文件字串,以支援在沒有文件字串的情況下構建 Python,如 PEP 7 中指定。示例
PyDoc_STRVAR(pop_doc, "Remove and return the rightmost element."); static PyMethodDef deque_methods[] = { // ... {"pop", (PyCFunction)deque_pop, METH_NOARGS, pop_doc}, // ... }
物件、型別和引用計數¶
大多數 Python/C API 函式都有一個或多個型別為 PyObject* 的引數和返回值。這種型別是指向一個不透明資料型別的指標,該型別表示任意 Python 物件。由於在大多數情況下(例如,賦值、作用域規則和引數傳遞)所有 Python 物件型別都由 Python 語言以相同的方式處理,因此它們應該由單個 C 型別表示是恰當的。幾乎所有 Python 物件都存在於堆上:您從不宣告型別為 PyObject
的自動或靜態變數,只能宣告型別為 PyObject* 的指標變數。唯一的例外是型別物件;由於它們絕不應該被釋放,因此它們通常是靜態的 PyTypeObject
物件。
所有 Python 物件(甚至 Python 整數)都有一個型別和一個引用計數。物件的型別決定了它是什麼型別的物件(例如,整數、列表或使用者定義函式;標準型別層級結構中解釋了更多)。對於每個眾所周知的型別,都有一個宏來檢查物件是否是該型別;例如,如果(且僅當)由 a 指向的物件是 Python 列表,則 PyList_Check(a)
為真。
引用計數¶
引用計數很重要,因為當今計算機的記憶體大小是有限的(而且通常受到嚴重限制);它計算有多少不同的地方對一個物件持有強引用。這樣的地方可以是另一個物件,或者一個全域性(或靜態)C 變數,或者某個 C 函式中的區域性變數。當對一個物件的最後一個強引用被釋放時(即其引用計數變為零),該物件就會被解除分配。如果它包含對其他物件的引用,那些引用也會被釋放。如果對這些其他物件不再有引用,它們也可能被解除分配,依此類推。(這裡有一個明顯的相互引用的物件問題;目前,解決方案是“不要那樣做。”)
引用計數總是顯式地操作。通常的方法是使用宏 Py_INCREF()
來獲取一個物件的新引用(即將其引用計數增加一),以及 Py_DECREF()
來釋放該引用(即將其引用計數減少一)。Py_DECREF()
宏比 incref 宏複雜得多,因為它必須檢查引用計數是否變為零,然後導致呼叫物件的解分配器。解分配器是物件型別結構中包含的一個函式指標。型別特定的解分配器負責釋放物件中包含的其他物件的引用(如果這是一個複合物件型別,例如列表),以及執行所需的任何其他最終化操作。引用計數不可能溢位;至少使用與虛擬記憶體中不同記憶體位置一樣多的位來儲存引用計數(假設 sizeof(Py_ssize_t) >= sizeof(void*)
)。因此,引用計數增加是一個簡單的操作。
並非每個包含物件指標的區域性變數都需要持有強引用(即增加引用計數)。理論上,當變數指向物件時,物件的引用計數會增加一;當變數超出作用域時,引用計數會減少一。然而,這兩個操作會相互抵消,因此最終引用計數沒有改變。使用引用計數的唯一真正原因是防止物件在我們的變數指向它期間被解除分配。如果我們知道至少有一個對物件的其他引用,並且其生命週期至少與我們的變數一樣長,則不需要暫時獲取新的強引用(即增加引用計數)。一個重要的情況是在透過擴充套件模組中的 C 函式作為引數傳遞給 Python 呼叫的物件;呼叫機制保證在呼叫期間持有對每個引數的引用。
然而,一個常見的陷阱是從列表中提取一個物件,並在不獲取新引用的情況下持有它一段時間。其他一些操作可能會從列表中刪除該物件,釋放該引用,並可能解除分配它。真正的危險在於,看似無害的操作可能會呼叫任意 Python 程式碼,從而導致這種情況;存在一條程式碼路徑允許控制從 Py_DECREF()
流回到使用者,因此幾乎任何操作都可能具有潛在危險。
一個安全的方法是始終使用通用操作(名稱以 PyObject_
、PyNumber_
、PySequence_
或 PyMapping_
開頭的函式)。這些操作總是會建立它們返回物件的新的強引用(即增加引用計數)。這使得呼叫者有責任在完成使用結果後呼叫 Py_DECREF()
;這很快就會成為第二天性。
引用計數詳情¶
Python/C API 中函式的引用計數行為最好用引用的所有權來解釋。所有權涉及引用,而不是物件(物件不被擁有:它們總是共享的)。“擁有引用”意味著當不再需要引用時,負責對其呼叫 Py_DECREF。所有權也可以轉移,這意味著接收引用所有權的程式碼隨後負責在不再需要時透過呼叫 Py_DECREF()
或 Py_XDECREF()
來最終釋放它——或者將此責任傳遞(通常是給其呼叫者)。當函式將其引用所有權傳遞給其呼叫者時,稱呼叫者收到一個新引用。當沒有所有權轉移時,稱呼叫者借用該引用。對於借用引用,不需要做任何事情。
反之,當一個呼叫函式傳入一個物件引用時,有兩種可能性:函式偷取了對該物件的引用,或者它沒有。_偷取引用_意味著當你將一個引用傳遞給一個函式時,該函式假定它現在擁有該引用,你不再對其負責。
很少有函式會偷取引用;兩個顯著的例外是 PyList_SetItem()
和 PyTuple_SetItem()
,它們會偷取對項的引用(但不是對項所放入的元組或列表的引用!)。這些函式被設計為偷取引用,因為它們常見於用新建立的物件填充元組或列表的慣用寫法;例如,建立元組 (1, 2, "three")
的程式碼可能如下所示(暫時忽略錯誤處理;下面展示了更好的編寫方式)
PyObject *t;
t = PyTuple_New(3);
PyTuple_SetItem(t, 0, PyLong_FromLong(1L));
PyTuple_SetItem(t, 1, PyLong_FromLong(2L));
PyTuple_SetItem(t, 2, PyUnicode_FromString("three"));
在這裡,PyLong_FromLong()
返回一個新引用,該引用立即被 PyTuple_SetItem()
偷走。當您想繼續使用一個物件,儘管對其的引用將被偷走時,請在呼叫偷取引用的函式之前使用 Py_INCREF()
來獲取另一個引用。
順便說一句,PyTuple_SetItem()
是設定元組項的唯一方法;PySequence_SetItem()
和 PyObject_SetItem()
拒絕這樣做,因為元組是不可變資料型別。您應該只對您自己建立的元組使用 PyTuple_SetItem()
。
使用 PyList_New()
和 PyList_SetItem()
可以編寫用於填充列表的等效程式碼。
然而,在實踐中,您很少會使用這些建立和填充元組或列表的方式。有一個通用函式 Py_BuildValue()
,它可以透過格式字串從 C 值建立大多數常見物件。例如,上面兩段程式碼可以替換為以下內容(它也處理了錯誤檢查)
PyObject *tuple, *list;
tuple = Py_BuildValue("(iis)", 1, 2, "three");
list = Py_BuildValue("[iis]", 1, 2, "three");
更常見的情況是,將 PyObject_SetItem()
及相關函式用於您只是借用的項(例如傳遞給您正在編寫的函式的引數)。在這種情況下,它們關於引用的行為更加合理,因為您不必為了放棄引用(“讓它被偷走”)而獲取新引用。例如,此函式將列表(實際上是任何可變序列)的所有項設定為給定項
int
set_all(PyObject *target, PyObject *item)
{
Py_ssize_t i, n;
n = PyObject_Length(target);
if (n < 0)
return -1;
for (i = 0; i < n; i++) {
PyObject *index = PyLong_FromSsize_t(i);
if (!index)
return -1;
if (PyObject_SetItem(target, index, item) < 0) {
Py_DECREF(index);
return -1;
}
Py_DECREF(index);
}
return 0;
}
函式返回值的情況略有不同。雖然將引用傳遞給大多數函式不會改變您對該引用的所有權責任,但許多返回物件引用的函式會賦予您對該引用的所有權。原因很簡單:在許多情況下,返回的物件是即時建立的,您獲得的引用是該物件的唯一引用。因此,返回物件引用的通用函式,如 PyObject_GetItem()
和 PySequence_GetItem()
,總是返回一個新引用(呼叫者成為該引用的所有者)。
重要的是要認識到,您是否擁有函式返回的引用取決於您呼叫的函式——羽毛(作為引數傳遞給函式的物件的型別)與此無關!因此,如果您使用 PyList_GetItem()
從列表中提取一個項,您不擁有該引用——但如果您使用 PySequence_GetItem()
從同一列表中獲取同一項(該函式恰好接受完全相同的引數),您確實擁有對返回物件的引用。
這裡有一個示例,說明如何編寫一個函式來計算整數列表中各項的和;一個使用 PyList_GetItem()
,另一個使用 PySequence_GetItem()
。
long
sum_list(PyObject *list)
{
Py_ssize_t i, n;
long total = 0, value;
PyObject *item;
n = PyList_Size(list);
if (n < 0)
return -1; /* Not a list */
for (i = 0; i < n; i++) {
item = PyList_GetItem(list, i); /* Can't fail */
if (!PyLong_Check(item)) continue; /* Skip non-integers */
value = PyLong_AsLong(item);
if (value == -1 && PyErr_Occurred())
/* Integer too big to fit in a C long, bail out */
return -1;
total += value;
}
return total;
}
long
sum_sequence(PyObject *sequence)
{
Py_ssize_t i, n;
long total = 0, value;
PyObject *item;
n = PySequence_Length(sequence);
if (n < 0)
return -1; /* Has no length */
for (i = 0; i < n; i++) {
item = PySequence_GetItem(sequence, i);
if (item == NULL)
return -1; /* Not a sequence, or other failure */
if (PyLong_Check(item)) {
value = PyLong_AsLong(item);
Py_DECREF(item);
if (value == -1 && PyErr_Occurred())
/* Integer too big to fit in a C long, bail out */
return -1;
total += value;
}
else {
Py_DECREF(item); /* Discard reference ownership */
}
}
return total;
}
型別¶
Python/C API 中還有少數其他資料型別扮演著重要角色;大多數是簡單的 C 型別,例如 int、long、double 和 char*。少數結構型別用於描述用於列出模組匯出的函式或新物件型別的資料屬性的靜態表,另一種用於描述複數的值。這些將在討論使用它們的函式時一併討論。
-
type Py_ssize_t¶
- 作為 穩定 ABI 的一部分。
一個有符號整數型別,其中
sizeof(Py_ssize_t) == sizeof(size_t)
。C99 沒有直接定義這樣的型別 (size_t 是一個無符號整數型別)。有關詳細資訊,請參閱 PEP 353。PY_SSIZE_T_MAX
是Py_ssize_t
型別的最大正值。
異常¶
Python 程式設計師只需要在需要特定錯誤處理時處理異常;未處理的異常會自動傳播給呼叫者,然後傳播給呼叫者的呼叫者,依此類推,直到它們到達頂層直譯器,在那裡它們會連同堆疊回溯一起報告給使用者。
然而,對於 C 程式設計師來說,錯誤檢查總是必須顯式的。Python/C API 中的所有函式都可能引發異常,除非函式文件中另有明確說明。通常,當函式遇到錯誤時,它會設定一個異常,丟棄其擁有的任何物件引用,並返回一個錯誤指示符。如果未另行說明,此指示符要麼是 NULL
,要麼是 -1
,具體取決於函式的返回型別。少數函式返回布林真/假結果,其中假表示錯誤。極少數函式不返回明確的錯誤指示符或具有模糊的返回值,並且需要使用 PyErr_Occurred()
進行顯式錯誤測試。這些異常總是明確記錄的。
異常狀態儲存在每個執行緒的儲存中(這相當於在非執行緒應用程式中使用全域性儲存)。一個執行緒可以處於兩種狀態之一:發生異常,或者沒有發生異常。PyErr_Occurred()
函式可用於檢查此情況:當發生異常時,它返回一個借用異常型別物件的引用,否則返回 NULL
。有許多函式可以設定異常狀態:PyErr_SetString()
是最常見的(儘管不是最通用的)設定異常狀態的函式,而 PyErr_Clear()
清除異常狀態。
完整的異常狀態由三個物件組成(所有這些物件都可以是 NULL
):異常型別、相應的異常值和回溯。它們與 sys.exc_info()
的 Python 結果具有相同的含義;但是,它們並不相同:Python 物件表示 Python try
… except
語句正在處理的最後一個異常,而 C 級異常狀態僅在異常在 C 函式之間傳遞時存在,直到它到達 Python 位元組碼直譯器的主迴圈,該主迴圈負責將其傳輸到 sys.exc_info()
等。
請注意,從 Python 1.5 開始,從 Python 程式碼訪問異常狀態的首選、執行緒安全方式是呼叫函式 sys.exc_info()
,它返回 Python 程式碼的每執行緒異常狀態。此外,兩種訪問異常狀態的方式的語義都已更改,以便捕獲異常的函式將儲存並恢復其執行緒的異常狀態,以保留其呼叫者的異常狀態。這可以防止由看似無害的函式覆蓋正在處理的異常而導致的常見異常處理錯誤;它還減少了對回溯中堆疊幀引用的物件的通常不必要的生命週期延長。
一般來說,呼叫另一個函式執行某項任務的函式應該檢查被呼叫函式是否引發了異常,如果是,則將異常狀態傳遞給其呼叫者。它應該丟棄其擁有的任何物件引用,並返回一個錯誤指示符,但它不應該設定另一個異常——那會覆蓋剛剛引發的異常,並丟失有關錯誤確切原因的重要資訊。
上面 sum_sequence()
示例中展示了一個檢測和傳遞異常的簡單示例。恰好此示例在檢測到錯誤時不需要清理任何擁有的引用。以下示例函式展示了一些錯誤清理。首先,為了提醒您為什麼喜歡 Python,我們展示了等效的 Python 程式碼
def incr_item(dict, key):
try:
item = dict[key]
except KeyError:
item = 0
dict[key] = item + 1
這是相應的 C 程式碼,所有細節都一覽無餘
int
incr_item(PyObject *dict, PyObject *key)
{
/* Objects all initialized to NULL for Py_XDECREF */
PyObject *item = NULL, *const_one = NULL, *incremented_item = NULL;
int rv = -1; /* Return value initialized to -1 (failure) */
item = PyObject_GetItem(dict, key);
if (item == NULL) {
/* Handle KeyError only: */
if (!PyErr_ExceptionMatches(PyExc_KeyError))
goto error;
/* Clear the error and use zero: */
PyErr_Clear();
item = PyLong_FromLong(0L);
if (item == NULL)
goto error;
}
const_one = PyLong_FromLong(1L);
if (const_one == NULL)
goto error;
incremented_item = PyNumber_Add(item, const_one);
if (incremented_item == NULL)
goto error;
if (PyObject_SetItem(dict, key, incremented_item) < 0)
goto error;
rv = 0; /* Success */
/* Continue with cleanup code */
error:
/* Cleanup code, shared by success and failure path */
/* Use Py_XDECREF() to ignore NULL references */
Py_XDECREF(item);
Py_XDECREF(const_one);
Py_XDECREF(incremented_item);
return rv; /* -1 for error, 0 for success */
}
這個例子代表了 C 語言中 goto
語句的一種受認可的用法!它說明了使用 PyErr_ExceptionMatches()
和 PyErr_Clear()
來處理特定異常,以及使用 Py_XDECREF()
來處理可能為 NULL
的自有引用(注意名稱中的 'X'
;Py_DECREF()
在遇到 NULL
引用時會崩潰)。重要的是,用於儲存自有引用的變數必須初始化為 NULL
才能正常工作;同樣,建議的返回值初始化為 -1
(失敗),並且僅在最終呼叫成功後才設定為成功。
嵌入 Python¶
Python 直譯器的嵌入者(與擴充套件模組編寫者不同)唯一需要關注的重要任務是 Python 直譯器的初始化,以及可能的最終化。直譯器的大多數功能只有在直譯器初始化後才能使用。
基本的初始化函式是 Py_Initialize()
。它初始化已載入模組的表,並建立基本模組 builtins
、__main__
和 sys
。它還初始化模組搜尋路徑 (sys.path
)。
Py_Initialize()
不會設定“指令碼引數列表”(sys.argv
)。如果以後執行的 Python 程式碼需要此變數,則必須設定 PyConfig.argv
和 PyConfig.parse_argv
:請參閱Python 初始化配置。
在大多數系統上(特別是 Unix 和 Windows,儘管細節略有不同),Py_Initialize()
根據其對標準 Python 直譯器可執行檔案位置的最佳猜測來計算模組搜尋路徑,假設 Python 庫相對於 Python 直譯器可執行檔案位於固定位置。特別是,它會在 shell 命令搜尋路徑(環境變數 PATH
)中查詢名為 python
的可執行檔案所在父目錄下的名為 lib/pythonX.Y
的目錄。
例如,如果 Python 可執行檔案在 /usr/local/bin/python
中找到,它會假定庫在 /usr/local/lib/pythonX.Y
中。(實際上,此特定路徑也是“回退”位置,當在 PATH
中找不到名為 python
的可執行檔案時使用。)使用者可以透過設定環境變數 PYTHONHOME
來覆蓋此行為,或者透過設定 PYTHONPATH
在標準路徑之前插入其他目錄。
嵌入式應用程式可以透過在呼叫 Py_InitializeFromConfig()
之前設定 PyConfig.program_name
來引導搜尋。請注意,PYTHONHOME
仍會覆蓋此設定,並且 PYTHONPATH
仍會插入到標準路徑之前。需要完全控制的應用程式必須提供自己的 Py_GetPath()
、Py_GetPrefix()
、Py_GetExecPrefix()
和 Py_GetProgramFullPath()
實現(所有這些都在 Modules/getpath.c
中定義)。
有時,希望“反初始化”Python。例如,應用程式可能想重新開始(再次呼叫 Py_Initialize()
),或者應用程式只是完成了對 Python 的使用並希望釋放 Python 分配的記憶體。這可以透過呼叫 Py_FinalizeEx()
來實現。Py_IsInitialized()
函式返回 Python 當前是否處於初始化狀態。有關這些函式的更多資訊將在後面的章節中給出。請注意,Py_FinalizeEx()
不會釋放 Python 直譯器分配的所有記憶體,例如,擴充套件模組分配的記憶體目前無法釋放。
除錯構建¶
Python 可以使用多個宏構建,以啟用對直譯器和擴充套件模組的額外檢查。這些檢查往往會增加大量的執行時開銷,因此預設情況下不啟用。
有關各種除錯構建的完整列表,請參見 Python 原始碼分發中的 Misc/SpecialBuilds.txt
檔案。有支援引用計數跟蹤、除錯記憶體分配器或主直譯器迴圈低階分析的構建。本節的其餘部分將僅描述最常用的構建。
-
Py_DEBUG¶
使用 Py_DEBUG
宏編譯直譯器會產生通常所說的Python 的除錯構建。在 Unix 構建中,透過將 --with-pydebug
新增到 ./configure
命令來啟用 Py_DEBUG
。它也隱含在非 Python 特定宏 _DEBUG
的存在中。當在 Unix 構建中啟用 Py_DEBUG
時,編譯器最佳化被停用。
除了下面描述的引用計數除錯之外,還執行額外的檢查,請參閱Python 除錯構建。
定義 Py_TRACE_REFS
可啟用引用跟蹤(請參閱 configure --with-trace-refs 選項
)。定義後,透過為每個 PyObject
新增兩個額外欄位來維護活動物件的雙向迴圈連結串列。總分配量也會被跟蹤。退出時,會列印所有現有引用。(在互動模式下,這在直譯器執行的每個語句之後發生。)
有關更詳細的資訊,請參閱 Python 原始碼分發中的 Misc/SpecialBuilds.txt
。
推薦的第三方工具¶
以下第三方工具提供了更簡單和更復雜的建立 Python C、C++ 和 Rust 擴充套件的方法
使用此類工具可以幫助避免編寫與特定 CPython 版本緊密繫結的程式碼,避免引用計數錯誤,並更多地關注您自己的程式碼而不是使用 CPython API。通常,可以透過更新工具來支援新版本的 Python,並且您的程式碼通常會自動使用更新和更高效的 API。一些工具還支援從一組源編譯用於其他 Python 實現。
這些專案不受維護 Python 的人員支援,問題需要直接向這些專案提出。請記住檢查專案是否仍在維護和支援,因為上面的列表可能會過時。
參見
- Python 打包使用者指南:二進位制擴充套件
Python 打包使用者指南不僅涵蓋了幾種可用於簡化二進位制擴充套件建立的工具,還討論了最初建立擴充套件模組的各種原因。