擴充/嵌入常見問題集
*******************


我可以在 C 中建立自己的函式嗎？
===============================

是的，你可以在 C 中建立包含函式、變數、例外甚至新型別的內建模組，擴充
和嵌入 Python 直譯器 文件中有相關說明。

大多數中級或進階 Python 書籍也會涵蓋這個主題。


我可以在 C++ 中建立自己的函式嗎？
=================================

是的，可使用 C++ 中的 C 相容性功能。將 "extern "C" { ... }" 放在
Python 引入檔案周圍，並將 "extern "C"" 放在每個將由 Python 直譯器呼叫
的函式之前。但具有構造函式的全域或靜態 C++ 物件可能不是一個好主意。


寫 C 很難；還有其他選擇嗎？
===========================

There are a number of alternatives to writing your own C extensions,
depending on what you're trying to do. Recommended third party tools
offer both simpler and more sophisticated approaches to creating C and
C++ extensions for Python.


如何從 C 執行任意 Python 陳述式？
=================================

執行此操作的最高階函式是 "PyRun_SimpleString()"，它接受一個要在模組
"__main__" 的情境中執行的單一字串引數，成功時回傳 "0"，發生例外（包括
"SyntaxError"）時回傳 "-1"。如果你想要更多控制，請使用
"PyRun_String()"；請參閱 "Python/pythonrun.c" 中
"PyRun_SimpleString()" 的原始碼。


如何計算來自 C 的任意 Python 運算式？
=====================================

呼叫前一個問題中的 "PyRun_String()" 函式，並使用起始符號
"Py_eval_input"；它會剖析一個運算式，計算它並回傳它的值。


如何從 Python 物件中提取 C 值？
===============================

這取決於物件的型別。如果它是一個元組，"PyTuple_Size()" 會回傳它的長度
，"PyTuple_GetItem()" 則回傳指定索引的項目。串列具有類似的函式
"PyList_Size()" 和 "PyList_GetItem()"。

對於位元組，"PyBytes_Size()" 會回傳它的長度，
"PyBytes_AsStringAndSize()" 則提供指向該值與該長度的指標。請注意，
Python 位元組物件可能包含空位元組，因此不應使用 C 的 "strlen()"。

要測試物件的型別，首先確保它不是 "NULL"，然後再使用 "PyBytes_Check()"
、"PyTuple_Check()"、"PyList_Check()" 等函式。

還有一個針對 Python 物件的高階 API，它由所謂的「抽象」介面所提供 —— 請
閱讀 "Include/abstract.h" 以了解更多詳細資訊。它允許使用
"PySequence_Length()"、"PySequence_GetItem()" 等函式的呼叫以及許多其他
有用的協定，例如數值（"PyNumber_Index()" 等）和 PyMapping API 中的對映
，來與任何類型的 Python 序列做介接。


如何使用 Py_BuildValue() 建立任意長度的元組？
=============================================

這無法做到。請改用 "PyTuple_Pack()"。


如何從 C 呼叫物件的方法？
=========================

"PyObject_CallMethod()" 函式可用於呼叫物件的任意方法。參數是物件、要呼
叫的方法名稱、與 "Py_BuildValue()" 一起使用的格式字串，以及引數值：

   PyObject *
   PyObject_CallMethod(PyObject *object, const char *method_name,
                       const char *arg_format, ...);

這適用於任何具有方法的物件 —— 無論是內建的還是使用者定義的。你負責最終
為回傳值來 "Py_DECREF()"。

例如，使用引數 10、0 呼叫檔案物件的 "seek" 方法（假設檔案物件指標為
"f"）：

   res = PyObject_CallMethod(f, "seek", "(ii)", 10, 0);
   if (res == NULL) {
           ... 發生一個例外 ...
   }
   else {
           Py_DECREF(res);
   }

請注意，由於 "PyObject_CallObject()" *總是*需要一個元組作為引數列表，
若要呼叫一個不帶引數的函式，要傳遞 "()" 作為格式，並呼叫一個帶有一個引
數的函式，將引數括起來在括號中，例如 "(i)"。


我如何捕捉 PyErr_Print() 的輸出（或任何印出到 stdout/stderr 的東西）？
======================================================================

在 Python 程式碼中定義一個支援 "write()" 方法的物件。將此物件分配給
"sys.stdout" 和 "sys.stderr"。呼叫 print_error，或者只允許標準的回溯機
制起作用。然後，輸出將會傳送到你的 "write()" 方法所指定的位置。

最簡單的方法是使用 "io.StringIO" 類別：

   >>> import io, sys
   >>> sys.stdout = io.StringIO()
   >>> print('foo')
   >>> print('hello world!')
   >>> sys.stderr.write(sys.stdout.getvalue())
   foo
   hello world!

執行相同操作的自訂物件如下所示：

   >>> import io, sys
   >>> class StdoutCatcher(io.TextIOBase):
   ...     def __init__(self):
   ...         self.data = []
   ...     def write(self, stuff):
   ...         self.data.append(stuff)
   ...
   >>> import sys
   >>> sys.stdout = StdoutCatcher()
   >>> print('foo')
   >>> print('hello world!')
   >>> sys.stderr.write(''.join(sys.stdout.data))
   foo
   hello world!


如何從 C 存取用 Python 編寫的模組？
===================================

你可以取得指向模組物件的指標，如下所示：

   module = PyImport_ImportModule("<modulename>");

如果模組還沒有被引入（即它還沒有出現在 "sys.modules" 中），這會初始化
模組；否則它只回傳 "sys.modules["<modulename>"]" 的值。請注意，它不會
將模組輸入任何命名空間——它只會確保它已被初始化並儲存在 "sys.modules"
中。

然後你可以存取模組的屬性（即模組中定義的任何名稱），如下所示：

   attr = PyObject_GetAttrString(module, "<attrname>");

呼叫 "PyObject_SetAttrString()" 來分配模組中的變數也有效。


我如何從 Python 介接到 C++ 物件？
=================================

根據你的要求不同而有多種不同方法。要手動執行此操作，請先閱讀「擴充和嵌
入」說明文件。對於 Python run-time 系統，C 和 C++ 之間並沒有太多區別
—— 因此圍繞 C 結構（指標）型別來構建新 Python 型別的策略也適用於 C++
物件。

對於 C++ 函式庫，請參閱 寫 C 很難；還有其他選擇嗎？。


我使用安裝檔案新增了一個模組，但 make 失敗了；為什麼？
======================================================

安裝程式必須以換行符結尾，如果那裡沒有換行符，構建過程將失敗。（解決這
個問題需要一些醜陋的 shell 腳本 hackery，而且這個錯誤很小，似乎不值得
付出努力。）


如何為擴充套件除錯？
====================

將 GDB 與動態載入的擴充一起使用時，在載入擴充之前不能在擴充中設定斷點
。

在你的 ".gdbinit" 檔案中（或交互地），新增命令：

   br _PyImport_LoadDynamicModule

然後，當你運行 GDB 時：

   $ gdb /local/bin/python
   gdb) run myscript.py
   gdb) continue # repeat until your extension is loaded
   gdb) finish   # so that your extension is loaded
   gdb) br myfunction.c:50
   gdb) continue


我想在我的 Linux 系統上編譯一個 Python 模組，但是缺少一些檔案。為什麼？
=======================================================================

大多數打包版本的 Python 省略了編譯 Python 擴充所需的一些檔案。

在 Red Hat 上，請安裝 python3-devel RPM 來取得必要的檔案。

對於 Debian，運行 "apt-get install python3-dev"。


如何從「無效輸入」區分出「不完整輸入」？
========================================

有時你會想模擬 Python 交互式直譯器的行為，當輸入不完整時（例如，當你輸
入了 "if" 陳述式的開頭或者你沒有關閉你的括號或三重字串引號）它會給你一
個繼續提示字元，但是當輸入無效時，它會立即為你提供語法錯誤訊息。

在 Python 中，你可以使用 "codeop" 模組，它充分模擬了剖析器 (parser) 的
行為。像是 IDLE 就有使用它。

在 C 中執行此操作的最簡單方法是呼叫 "PyRun_InteractiveLoop()"（可能是
在單獨的執行緒中）並讓 Python 直譯器為你處理輸入。你還可以將
"PyOS_ReadlineFunctionPointer()" 設定為指向你的自訂輸入函式。有關更多
提示，請參閱 "Modules/readline.c" 和 "Parser/myreadline.c"。


如何找到未定義的 g++ 符號 __builtin_new 或 __pure_virtual？
===========================================================

要動態載入 g++ 擴充模組，你必須重新編譯 Python，並使用 g++ 重新鏈接它
（更改 Python 模組 Makefile 中的 LINKCC），且使用 g++ 鏈接你的擴充模組
（例如，"g++ -shared -o mymodule.so mymodule.o"）。


我可以用一些用 C 實作的方法和用 Python 實作的其他方法（例如透過繼承）建立一個物件類別嗎？
=========================================================================================

是的，你可以繼承內建類別，例如 "int"、"list"、"dict" 等。

Boost Python 函式庫（BPL，
https://www.boost.org/libs/python/doc/index.html）提供了一種從 C++ 執
行此操作的方法（即你可以使用 BPL 來繼承用 C++ 編寫的擴充類別）。
