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


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

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

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


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

是的，可以使用 C++ 中兼容 C 的功能。 在 Python include 文件周围放置
"extern "C" { ... }" ，并在Python解释器调用的每个函数之前放置 "extern
"C"" 。 具有构造函数的全局或静态 C++ 对象可能不是一个好主意。


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

要編寫你自己的 C 擴充有許多替代方法，取決於你要執行的具體操作為何。

Cython 及其相关的 Pyrex 是接受略微修改过的 Python 形式并生成相应 C 代
码的编译器。 Cython 和 Pyrex 使得人们无需学习 Python 的 C API 即可编写
扩展。

如果你需要针对某些当前不存在 Python 扩展的 C 或 C++ 库的接口，你可以尝
试使用 SWIG 等工具来包装这些库的数据类型和函数。 SIP, CXX Boost 或者
Weave 也是用于包装 C++ 库的替代方案。


如何從 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()" 这样的调用来与任意种类的 Python 序列进行对接，
此外还可使用许多其他有用的协议例如数字 ("PyNumber_Index()" 等) 以及
PyMapping API 中的各种映射等等。


如何使用 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) {
           ... an exception occurred ...
   }
   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 运行时系统来说，C 和 C++ 并不没有
太大的区别 —— 因此围绕一个 C 结构（指针）类型构建新 Python 对象的策略
同样适用于 C++ 对象。

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


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

安装程序必须以换行符结束，如果没有换行符，则构建过程将失败。 （修复这
个需要一些丑陋的shell脚本编程，而且这个bug很小，看起来不值得花这么大力
气。)


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

将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版本不包含 "/usr/lib/python2.*x*/config/" 目录，该目
录中包含编译Python扩展所需的各种文件。

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

對於 Debian，運行 "apt-get install python-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 Modules Makefile中更改LINKCC），及链接扩展模块（例如： "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++ 編寫的擴充類別）。
