簡介
****

對於 Python 的應用程式開發介面使得 C 和 C++ 開發者能夠在各種層級存取
Python 直譯器。該 API 同樣可用於 C++，但為簡潔起見，通常將其稱為
Python/C API。使用 Python/C API 有兩個不同的原因，第一個是為特定目的來
編寫*擴充模組*；這些是擴充 Python 直譯器的 C 模組，這可能是最常見的用
法。第二個原因是在更大的應用程式中將 Python 作為零件使用；這種技術通常
在應用程式中稱為 *embedding*（嵌入式）Python。

編寫擴充模組是一個相對容易理解的過程，其中「食譜 (cookbook)」方法很有
效。有幾種工具可以在一定程度上自動化該過程，儘管人們從早期就將 Python
嵌入到其他應用程式中，但嵌入 Python 的過程並不像編寫擴充那樣簡單。

不論你是嵌入還是擴充 Python，許多 API 函式都是很有用的；此外，大多數嵌
入 Python 的應用程式也需要提供自訂擴充模組，因此在嘗試將 Python 嵌入實
際應用程式之前熟悉編寫擴充可能是個好主意。


語言版本相容性
==============

Python 的 C API 與 C11 和 C++11 版本的 C 和 C++ 相容。

This is a lower limit: the C API does not require features from later
C/C++ versions. You do *not* need to enable your compiler's "c11
mode".


編寫標準
========

如果你正在編寫要引入於 CPython 中的 C 程式碼，你**必須**遵循 **PEP 7**
中定義的指南和標準。無論你貢獻的 Python 版本如何，這些指南都適用。對於
你自己的第三方擴充模組，則不必遵循這些約定，除非你希望最終將它們貢獻給
Python。


引入檔案 (include files)
========================

使用 Python/C API 所需的所有函式、型別和巨集的定義都透過以下這幾行來在
你的程式碼中引入：

   #define PY_SSIZE_T_CLEAN
   #include <Python.h>

這意味著會引入以下標準標頭："<stdio.h>"、"<string.h>"、"<errno.h>"、
"<limits.h>"、"<assert.h>" 和 "<stdlib.h>"（如果可用）。

備註:

  由於 Python 可能會定義一些會影響某些系統上標準標頭檔的預處理器 (pre-
  processor)，因此你*必須*在引入任何標準標頭檔之前引入 "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" 是指定給安裝程式 (installer) 用的安裝目錄。

要引入標頭，請將兩個（如果不同）目錄放在編譯器的引入搜尋路徑 (search
path) 中。*不要*將父目錄放在搜尋路徑上，然後使用 "#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" 的絕對值。

   If the result cannot be represented (for example, if "x" has
   "INT_MIN" value for int type), the behavior is undefined.

   在 3.3 版被加入.

Py_ALWAYS_INLINE

   要求編譯器總是嵌入靜態行內函式 (static inline function)，編譯器可以
   忽略它並決定不嵌入該函式。

   在禁用函式嵌入的除錯模式下建置 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] 範圍內的字元或整數。這個巨集會將
   "c" 轉換為 "unsigned char" 並回傳。

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_LOCAL(type)

   Declare a function returning the specified *type* using a fast-
   calling qualifier for functions that are local to the current file.
   Semantically, this is equivalent to "static type".

Py_LOCAL_INLINE(type)

   Equivalent to "Py_LOCAL" but additionally requests the function be
   inlined.

Py_LOCAL_SYMBOL

   Macro used to declare a symbol as local to the shared library
   (hidden). On supported platforms, it ensures the symbol is not
   exported.

   On compatible versions of GCC/Clang, it expands to
   "__attribute__((visibility("hidden")))".

Py_MAX(x, y)

   回傳 "x" 和 "y" 之間的最大值。

   在 3.3 版被加入.

Py_MEMBER_SIZE(type, member)

   以位元組為單位回傳結構 ("type") "member" 的大小。

   在 3.6 版被加入.

Py_MEMCPY(dest, src, n)

   這是 "memcpy()" 的已*軟性棄用*別名。請直接使用 "memcpy()"。

   在 3.14 版之後被棄用: 這個巨集已被*軟性棄用*。

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()

   當你的設計中有無法達到的程式碼路徑時，請使用此選項。例如在 "case"
   語句已涵蓋了所有可能值的 "switch" 陳述式中的 "default:" 子句。在你
   可能想要呼叫 "assert(0)" 或 "abort()" 的地方使用它。

   在發布模式 (release mode) 下，巨集幫助編譯器最佳化程式碼，並避免有
   關無法存取程式碼的警告。例如該巨集是在發布模式下於 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 版被加入.

Py_BUILD_ASSERT(cond)

   Asserts a compile-time condition *cond*, as a statement. The build
   will fail if the condition is false or cannot be evaluated at
   compile time.

   範例：

      Py_BUILD_ASSERT(sizeof(PyTime_t) == sizeof(int64_t));

   在 3.3 版被加入.

Py_BUILD_ASSERT_EXPR(cond)

   Asserts a compile-time condition *cond*, as an expression that
   evaluates to "0". The build will fail if the condition is false or
   cannot be evaluated at compile time.

   範例：

      #define foo_to_char(foo) \
          ((char *)(foo) + Py_BUILD_ASSERT_EXPR(offsetof(struct foo, string) == 0))

   在 3.3 版被加入.

PyDoc_STRVAR(name, str)

   建立一個名為 *name* 的變數，可以在文件字串中使用。如果 Python 是在
   沒有文件字串的情況下建置，則該值將為空。

   如 **PEP 7** 中所指明，使用 "PyDoc_STRVAR" 作為文件字串可以支援在沒
   有文件字串的情況下建置 Python。

   範例：

      PyDoc_STRVAR(pop_doc, "Remove and return the rightmost element.");

      static PyMethodDef deque_methods[] = {
          // ...
          {"pop", (PyCFunction)deque_pop, METH_NOARGS, pop_doc},
          // ...
      }

PyDoc_STR(str)

   為給定的輸入字串建立一個文件字串，如果文件字串被禁用則建立空字串。

   如 **PEP 7** 中所指明，使用 "PyDoc_STR" 指定文件字串以支援在沒有文
   件字串下建置 Python。

   範例：

      static PyMethodDef pysqlite_row_methods[] = {
          {"keys", (PyCFunction)pysqlite_row_keys, METH_NOARGS,
              PyDoc_STR("Returns the keys of the row.")},
          {NULL, NULL}
      };

PyDoc_VAR(name)

   Declares a static character array variable with the given name
   *name*.

   範例：

      PyDoc_VAR(python_doc) = PyDoc_STR("A genus of constricting snakes in the Pythonidae family native "
                                        "to the tropics and subtropics of the Eastern Hemisphere.");

Py_ARRAY_LENGTH(array)

   Compute the length of a statically allocated C array at compile
   time.

   The *array* argument must be a C array with a size known at compile
   time. Passing an array with an unknown size, such as a heap-
   allocated array, will result in a compilation error on some
   compilers, or otherwise produce incorrect results.

   這大致上相當於：

      sizeof(array) / sizeof((array)[0])

Py_EXPORTED_SYMBOL

   Macro used to declare a symbol (function or data) as exported. On
   Windows, this expands to "__declspec(dllexport)". On compatible
   versions of GCC/Clang, it expands to
   "__attribute__((visibility("default")))". This macro is for
   defining the C API itself; extension modules should not use it.

Py_IMPORTED_SYMBOL

   Macro used to declare a symbol as imported. On Windows, this
   expands to "__declspec(dllimport)". This macro is for defining the
   C API itself; extension modules should not use it.

PyAPI_FUNC(type)

   Macro used by CPython to declare a function as part of the C API.
   Its expansion depends on the platform and build configuration. This
   macro is intended for defining CPython's C API itself; extension
   modules should not use it for their own symbols.

PyAPI_DATA(type)

   Macro used by CPython to declare a public global variable as part
   of the C API. Its expansion depends on the platform and build
   configuration. This macro is intended for defining CPython's C API
   itself; extension modules should not use it for their own symbols.


物件、型別和參照計數
====================

大多數 Python/C API 函式都有一個或多個引數以及一個型別為 PyObject* 的
回傳值，此型別是一個指標，指向一個表示任意 Python 物件的晦暗 (opaque)
資料型別。由於在大多數情況下，Python 語言以相同的方式處理所有 Python
物件型別（例如賦值、作用域規則和引數傳遞），因此它們應該由單個 C 型別
來表示。幾乎所有的 Python 物件都存在於堆積 (heap) 中：你永遠不會聲明
"PyObject" 型別的自動變數或靜態變數，只能聲明 PyObject* 型別的指標變數
。唯一的例外是型別物件；由於它們絕不能被釋放，因此它們通常是靜態
"PyTypeObject" 物件。

所有 Python 物件（甚至是 Python 整數）都有一個型別 (*type*) 和一個參照
計數 (*reference count*)。一個物件的型別決定了它是什麼種類的物件（例如
一個整數、一個 list 或一個使用者定義的函式；還有更多型別，請見標準型別
階層）。對於每個眾所周知的型別，都有一個巨集來檢查物件是否屬於該型別；
例如，若（且唯若）*a* 指向的物件是 Python list 時，"PyList_Check(a)"
為真。


參照計數
--------

參照計數很重要，因為現今的電腦記憶體大小是有限的（而且通常是非常有限的
）；它計算有多少個不同的地方用有了一個物件的*強參照*。這樣的地方可以是
另一個物件，或者全域（或靜態）C 變數，或者某個 C 函式中的本地變數。當
一個物件的最後一個*強參照*被釋放時（即其的參照計數變為零），該物件將被
解除配置 (deallocated)。如果它包含對其他物件的參照，則它們的參照會被釋
放。如果這樣的釋放使得再也沒有任何對於它們的參照，則可以依次為那些其他
物件解除配置，依此類推。（此處相互參照物件的存在是個明顯的問題；目前，
解決方案是「就不要那樣做」。）

參照計數總是被明確地操作。正常的方法是使用巨集 "Py_INCREF()" 來取得對
於物件的參照（即參照計數加一），並使用巨集 "Py_DECREF()" 來釋放參照（
即將參照計數減一）。"Py_DECREF()" 巨集比 incref 巨集複雜得多，因為它必
須檢查參照計數是否變為零，然後呼叫物件的釋放器 (deallocator)。釋放器是
包含在物件型別結構中的函式指標。特定型別的釋放器，在如果是一個複合物件
型別（例如 list）時負責釋放物件中包含的其他物件的參照，並執行任何需要
的額外完結步驟。參照計數不可能溢出；至少與虛擬記憶體中用來保存參照計數
的不同記憶體位置數量一樣多的位元會被使用（假設 "sizeof(Py_ssize_t) >=
sizeof(void*)"）。因此參照計數增加是一個簡單的操作。

沒有必要為每個包含物件指標的本地變數物件都持有一個*強參照*（即增加參照
計數）。理論上，當變數指向它時，物件的參照計數會增加 1，而當變數離開作
用域時就會減少 1。然而這兩者會相互抵消，所以最後參照計數沒有改變。使用
參照計數的唯一真正原因是防止物件還有變數指向它時被解除配置。如果我們知
道至少有一個物件的其他參照生存了至少與我們的變數一樣久，就不需要臨時增
加建立新的*強參照*（即增加參照計數）。出現這種情況的一個重要情況是在從
Python 呼叫的擴充模組中作為引數傳遞給 C 函式的物件；呼叫機制保證在呼叫
期間保持對每個參數的參照。

然而，一個常見的陷阱是從一個 list 中提取一個物件並保留它一段時間而不取
得其參照。某些其他操作可能會從列表中刪除該物件，減少其參照計數並可能取
消分配它。真正的危險是看似無害的操作可能會叫用可以執行此操作的任意
Python 程式碼；有一個程式碼路徑允許控制權從 "Py_DECREF()" 回歸使用者，
因此幾乎任何操作都有潛在危險。

一種安全的方法是都使用通用 (generics) 操作（名稱以 "PyObject_"、
"PyNumber_"、"PySequence_" 或 "PyMapping_" 開頭的函式）。這些操作總是
建立新的對於它們回傳物件的*強參照*（即增加其參照計數）。這讓呼叫者有責
任在處理完結果後呼叫 "Py_DECREF()"；這就成為第二本質。


參照計數詳細資訊
~~~~~~~~~~~~~~~~

Python/C API 中函式的參照計數行為最好用*參照的所有權*來解釋。所有權附
屬於參照而非物件（物件並非被擁有，它們總是共享的）。「擁有參照」意味著
當不再需要該參照時，負責在其上呼叫 Py_DECREF。所有權也可以轉移，這意味
著接收參照所有權的程式碼最終會負責在不需要參照時透過呼叫 "Py_DECREF()"
或 "Py_XDECREF()" 釋放參照 --- 或者將這個責任再傳遞出去（通常是給它的
呼叫者）。當一個函式將參照的所有權傳遞給它的呼叫者時，呼叫者被稱為接收
到一個*新*參照。當沒有所有權轉移時，呼叫者被稱為*借用*參照。如果是*借
用參照*就不需要做任何事情。

相反地，當呼叫的函式傳入物件的參照時，有兩種可能性：函式有*竊取
(steal)* 物件的參照，或者沒有。 *竊取參照*意味著當你將參照傳遞給函式時
，該函式假定它現在擁有該參照，並且你不再對它負責。

很少有函式會竊取參照；兩個值得注意的例外是 "PyList_SetItem()" 和
"PyTuple_SetItem()"，它們竊取了對項目的參照（但不是對項目所在的 tuple
或 list 的參照！）。因為有著使用新建立的物件來增加 (populate) tuple 或
list 的習慣，這些函式旨在竊取參照；例如，建立 tuple "(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()" 是設定 tuple 項目的*唯一*方法；
"PySequence_SetItem()" 和 "PyObject_SetItem()" 拒絕這樣做，因為 tuple
是一種不可變 (immutable) 的資料型別。你應該只對你自己建立的 tuple 使用
"PyTuple_SetItem()"。

可以使用 "PyList_New()" 和 "PyList_SetItem()" 編寫用於填充列表的等效程
式碼。

但是在實際操作中你很少會使用這些方法來建立和增加 tuple 和 list。有一個
通用函式 "Py_BuildValue()" 可以從 C 值建立最常見的物件，由 *format
string* 引導。例如上面的兩個程式碼可以用以下程式碼替換（它還負責了錯誤
檢查）：

   PyObject *tuple, *list;

   tuple = Py_BuildValue("(iis)", 1, 2, "three");
   list = Py_BuildValue("[iis]", 1, 2, "three");

更常見的是以那些借用參照的項目來使用 "PyObject_SetItem()" 及其系列函式
，比如傳遞給你正在編寫的函式的引數。在那種情況下，他們關於參照的行為會
比較穩健，因為你不取得新的一個參照就可以放棄參照（「讓它被竊取」）。例
如，此函式將 list（實際上是任何可變序列）的所有項目設定於給定項目：

   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()"，總是回傳一個新的參照（呼叫者成為參照的所有者）
。

重要的是要意識到你是否擁有一個函式回傳的參照只取決於你呼叫哪個函式 ---
*羽毛 (plumage)*（作為引數傳遞給函式的物件之型別）*不會進入它！*因此，
如果你使用 "PyList_GetItem()" 從 list 中提取一個項目，你不會擁有其參照
--- 但如果你使用 "PySequence_GetItem()" 從同一 list 中取得相同的項目（
且恰好使用完全相同的引數），你確實會擁有對回傳物件的參照。

以下是一個範例，說明如何編寫函式來計算一個整數 list 中項目的總和；一次
使用 "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; /* 不是 list */
       for (i = 0; i < n; i++) {
           item = PyList_GetItem(list, i); /* 不會失敗 */
           if (!PyLong_Check(item)) continue; /* 跳過非整數 */
           value = PyLong_AsLong(item);
           if (value == -1 && PyErr_Occurred())
               /* 整數太大，無法放入 C long，退出 */
               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; /* 沒有長度 */
       for (i = 0; i < n; i++) {
           item = PySequence_GetItem(sequence, i);
           if (item == NULL)
               return -1; /* 不是序列或其他錯誤 */
           if (PyLong_Check(item)) {
               value = PyLong_AsLong(item);
               Py_DECREF(item);
               if (value == -1 && PyErr_Occurred())
                   /* 整數太大，無法放入 C long，退出 */
                   return -1;
               total += value;
           }
           else {
               Py_DECREF(item); /* 丟棄參照所有權 */
           }
       }
       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 開發者就只需要處理例外；未處理的例外會
自動傳遞給呼叫者，然後傳遞給呼叫者的呼叫者，依此類推，直到它們到達頂層
直譯器，在那裡它們透過堆疊回溯 (stack trace) 回報給使用者。

然而，對於 C 開發者來說，錯誤檢查總是必須是顯式的。除非在函式的文件中
另有明確聲明，否則 Python/C API 中的所有函式都可以引發例外。通常當一個
函式遇到錯誤時，它會設定一個例外，丟棄它擁有的任何物件參照，並回傳一個
錯誤指示器。如果沒有另外文件記錄，這個指示器要麼是 "NULL" 不然就是
"-1"，取決於函式的回傳型別。有些函式會回傳布林值 true/false 結果，
false 表示錯誤。很少有函式不回傳明確的錯誤指示器或者有不明確的回傳值，
而需要使用 "PyErr_Occurred()" 明確測試錯誤。這些例外都會被明確地記錄於
文件。

例外的狀態會在個別執行緒的儲存空間 (per-thread storage) 中維護（這相當
於在非執行緒應用程式中使用全域儲存空間）。執行緒可以處於兩種狀態之一：
發生例外或未發生例外。函式 "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 程式碼的個別
執行緒例外狀態。此外，兩種存取例外狀態方法的語義都發生了變化，因此捕獲
例外的函式將保存和恢復其執行緒的例外狀態，從而保留其呼叫者的例外狀態。
這可以防止例外處理程式碼中的常見錯誤，這些錯誤是由看似無辜的函式覆蓋了
正在處理的例外而引起的；它還替回溯中被堆疊框 (stack frame) 參照的物件
減少了通常不需要的生命週期延長。

作為一般原則，呼叫另一個函式來執行某些任務的函式應該檢查被呼叫函式是否
引發了例外，如果是，則將例外狀態傳遞給它的呼叫者。它應該丟棄它擁有的任
何物件參照，並回傳一個錯誤指示符，但它*不應該*設定另一個例外 --- 這將
覆蓋剛剛引發的例外，並丟失關於錯誤確切原因的重要資訊。

上面的 "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()" 不設定「腳本引數列表 (script argument list)」
("sys.argv")。如果稍後將要執行的 Python 程式碼需要此變數，則必須設定
"PyConfig.argv" 和 "PyConfig.parse_argv"，請見 Python 初始化配置。

在大多數系統上（特別是在 Unix 和 Windows 上，儘管細節略有不同），
"Py_Initialize()" 會假設Python 函式庫相對於 Python 直譯器可執行檔案的
位置固定，並根據其對標準 Python 直譯器可執行檔案位置的最佳猜測來計算模
組搜尋路徑。或者更詳細地說，它會在 shell 命令搜尋路徑（環境變數 "PATH"
）中找到名為 "python" 的可執行檔案，並在其父目錄中查找一個名為
"lib/python*X.Y*" 的目錄的相對位置。

例如，如果在 "/usr/local/bin/python" 中找到 Python 可執行檔案，它將假
定函式庫位於 "/usr/local/lib/python*X.Y*" 中。（事實上這個特定的路徑也
是「後備 (fallback)」位置，當在 "PATH" 中找不到名為 "python" 的可執行
檔案時使用。）使用者可以透過設定環境變數來覆蓋此行為 "PYTHONHOME"，或
者透過設定 "PYTHONPATH" 在標準路徑前面插入額外的目錄。

嵌入的應用程式可以透過在呼叫 "Py_InitializeFromConfig()" *之前*設定
"PyConfig.program_name" 來引導搜尋。請注意 "PYTHONHOME" 仍然覆蓋它並且
"PYTHONPATH" 仍然插入在標準路徑的前面。需要完全控制權的應用程式必須實
作自己的 "Py_GetPath()"、"Py_GetPrefix()"、"Py_GetExecPrefix()" 和
"Py_GetProgramFullPath()"（全部定義在 "Modules/getpath.c")。

有時會希望能夠「取消初始化 (uninitialize)」Python。例如，應用程式可能
想要重新開始（再次呼叫 "Py_Initialize()"）或者應用程式簡單地完成了對
Python 的使用並想要釋放 Python 分配的記憶體。這可以透過呼叫
"Py_FinalizeEx()" 來完成。如果 Python 目前處於初始化狀態，函式
"Py_IsInitialized()" 會回傳 true。有關這些功能的更多資訊將在後面的章節
中給出。請注意 "Py_FinalizeEx()" *不會*釋放由 Python 直譯器分配的所有
記憶體，例如目前無法釋放被擴充模組所分配的記憶體。


除錯建置
========

Python 可以在建置時使用多個巨集來啟用對直譯器和擴充模組的額外檢查，這
些檢查往往會在執行環境 (runtime) 增加大量開銷 (overhead)，因此預設情況
下不啟用它們。

Python 原始碼發佈版本中的 "Misc/SpecialBuilds.txt" 檔案有一份包含多種
除錯構置的完整列表，為支援追蹤參照計數、為記憶體分配器除錯或對主直譯器
迴圈進行低階分析的建置。本節的其餘部分將僅描述最常用的建置。

Py_DEBUG

使用定義的 "Py_DEBUG" 巨集編譯直譯器會生成 Python 的除錯建置。
"Py_DEBUG" 在 Unix 建置中要透過在 "./configure" 命令中加入 "--with-
pydebug" 來啟用。非 Python 限定的 "_DEBUG" 巨集的存在也暗示了這一點。
當 "Py_DEBUG" 在 Unix 建置中啟用時，編譯器最佳化會被禁用。

除了下面描述的參照計數除錯之外，還會執行額外的檢查，請參閱 Python 除錯
建置。

定義 "Py_TRACE_REFS" 來啟用參照追蹤（參見"設定 --with-trace-refs 選項"
）。當有定義時，透過向每個 "PyObject" 新增兩個額外欄位來維護有效物件的
循環雙向鍊表 (circular doubly linked list)。全體分配也有被追蹤。退出時
將印出所有現行參照。（在交互模式下，這發生在直譯器運行的每個陳述句之後
。）

有關更多詳細資訊，請參閱 Python 原始碼發布版中的
"Misc/SpecialBuilds.txt"。


建議的第三方工具
================

以下第三方工具提供了更簡單且更細緻的方法來為 Python 建立 C、C++ 和
Rust 擴充模組：

* Cython

* cffi

* HPy

* nanobind (C++)

* Numba

* pybind11 (C++)

* PyO3 (Rust)

* SWIG

Using tools such as these can help avoid writing code that is tightly
bound to a particular version of CPython, avoid reference counting
errors, and focus more on your own code than on using the CPython API.
In general, new versions of Python can be supported by updating the
tool, and your code will often use newer and more efficient APIs
automatically. Some tools also support compiling for other
implementations of Python from a single set of sources.

These projects are not supported by the same people who maintain
Python, and issues need to be raised with the projects directly.
Remember to check that the project is still maintained and supported,
as the list above may become outdated.

也參考:

  Python Packaging User Guide: Binary Extensions
     The Python Packaging User Guide not only covers several available
     tools that simplify the creation of binary extensions, but also
     discusses the various reasons why creating an extension module
     may be desirable in the first place.
