您使用任何編譯語言(如 C、C++ 或 Java)編寫的任何代碼都可以集成或導入到另一個 Python 腳本中。此代碼被視為“擴展”。
Python 擴展模塊只不過是一個普通的 C 庫。在 Unix 機器上,這些庫通常以.so結尾(用于共享對象)。在 Windows 機器上,您通常會看到.dll(用于動態鏈接庫)。
編寫擴展的先決條件
要開始編寫擴展,您將需要 Python 頭文件。
在 Unix 機器上,這通常需要安裝開發人員特定的包,例如python2.5-dev。
Windows 用戶在使用二進制 Python 安裝程序時將這些標頭作為包的一部分。
此外,假設您對 C 或 C++ 有很好的了解,可以使用 C 編程編寫任何 Python 擴展。
先看一個 Python 擴展
對于您第一次查看 Python 擴展模塊,您需要將您的代碼分為四部分 -
頭文件Python.h。
您要作為模塊接口公開的 C 函數。
將 Python 開發人員看到的函數名稱映射到擴展模塊內的 C 函數的表。
一個初始化函數。
頭文件Python.h
您需要在 C 源文件中包含Python.h頭文件,這使您可以訪問用于將模塊掛接到解釋器的內部 Python API。
確保在您可能需要的任何其他標頭之前包含 Python.h。您需要遵循包含要從 Python 調用的函數。
C 函數
函數的 C 實現的簽名始終采用以下三種形式之一 -
static PyObject *MyFunction( PyObject *self, PyObject *args );
static PyObject *MyFunctionWithKeywords(PyObject *self,
PyObject *args,
PyObject *kw);
static PyObject *MyFunctionWithNoArgs( PyObject *self );
前面的每個聲明都返回一個 Python 對象。Python 中沒有像 C 中那樣的void函數。如果您不希望函數返回值,請返回 Python 的None值的 C 等效項。Python 頭文件定義了一個宏 Py_RETURN_NONE,它為我們做這件事。
您的 C 函數的名稱可以是您喜歡的任何名稱,因為它們在擴展模塊之外永遠不會出現。它們被定義為靜態函數。
您的 C 函數通常通過將 Python 模塊和函數名稱組合在一起來命名,如下所示 -
static PyObject *module_func(PyObject *self, PyObject *args) {
/* Do your stuff here. */
Py_RETURN_NONE;
}
這是一個名為func的 Python 函數,位于模塊module中。您將把指向 C 函數的指針放入源代碼中通常出現的模塊的方法表中。
方法對照表
這個方法表是一個簡單的 PyMethodDef 結構數組。該結構看起來像這樣 -
struct PyMethodDef {
char *ml_name;
PyCFunction ml_meth;
int ml_flags;
char *ml_doc;
};
這是該結構成員的描述 -
ml_name - 這是 Python 解釋器在 Python 程序中使用時呈現的函數名稱。
ml_meth - 這必須是具有前一節中描述的任何一個簽名的函數的地址。
ml_flags - 這告訴解釋器 ml_meth 正在使用三個簽名中的哪一個。
該標志的值通常為 METH_VARARGS。
如果你想允許關鍵字參數進入你的函數,這個標志可以與 METH_KEYWORDS 進行按位或運算。
這也可以具有 METH_NOARGS 值,表示您不想接受任何參數。
ml_doc - 這是函數的文檔字符串,如果您不想寫一個,它可能是 NULL。
該表需要使用一個標記來終止,該標記由相應成員的 NULL 和 0 值組成。
例子
對于上述定義的函數,我們有以下方法映射表 -
static PyMethodDef module_methods[] = {
{ "func", (PyCFunction)module_func, METH_NOARGS, NULL },
{ NULL, NULL, 0, NULL }
};
初始化函數
擴展模塊的最后一部分是初始化函數。該函數在模塊加載時由 Python 解釋器調用。需要將函數命名為init Module,其中Module是模塊的名稱。
初始化函數需要從您將要構建的庫中導出。Python 頭文件定義 PyMODINIT_FUNC 以包含適當的咒語,以便在我們正在編譯的特定環境中發生這種情況。您所要做的就是在定義函數時使用它。
您的 C 初始化函數通常具有以下總體結構 -
PyMODINIT_FUNC initModule() {
Py_InitModule3(func, module_methods, "docstring...");
}
這是Py_InitModule3函數的描述 -
func - 這是要導出的函數。
module _methods - 這是上面定義的映射表名稱。
docstring - 這是您想在擴展中給出的評論。
把這一切放在一起看起來像下面這樣 -
#include <Python.h>
static PyObject *module_func(PyObject *self, PyObject *args) {
/* Do your stuff here. */
Py_RETURN_NONE;
}
static PyMethodDef module_methods[] = {
{ "func", (PyCFunction)module_func, METH_NOARGS, NULL },
{ NULL, NULL, 0, NULL }
};
PyMODINIT_FUNC initModule() {
Py_InitModule3(func, module_methods, "docstring...");
}
例子
一個利用上述所有概念的簡單示例 -
#include <Python.h>
static PyObject* helloworld(PyObject* self) {
return Py_BuildValue("s", "Hello, Python extensions!!");
}
static char helloworld_docs[] =
"helloworld( ): Any message you want to put here!!\n";
static PyMethodDef helloworld_funcs[] = {
{"helloworld", (PyCFunction)helloworld,
METH_NOARGS, helloworld_docs},
{NULL}
};
void inithelloworld(void) {
Py_InitModule3("helloworld", helloworld_funcs,
"Extension module example!");
}
這里Py_BuildValue函數用于構建 Python 值。將上述代碼保存在 hello.c 文件中。我們將看到如何編譯和安裝這個模塊以從 Python 腳本中調用。
構建和安裝擴展
distutils包使以標準方式分發 Python 模塊(純 Python 模塊和擴展模塊)變得非常容易。模塊以源代碼形式分發,并通過通常稱為setup.py的安裝腳本構建和安裝,如下所示。
對于上述模塊,您需要準備以下 setup.py 腳本 -
from distutils.core import setup, Extension
setup(name='helloworld', version='1.0', \
ext_modules=[Extension('helloworld', ['hello.c'])])
現在,使用以下命令,它將執行所有需要的編譯和鏈接步驟,使用正確的編譯器和鏈接器命令和標志,并將生成的動態庫復制到適當的目錄 -
$ python setup.py install
在基于 Unix 的系統上,您很可能需要以 root 身份運行此命令才能獲得寫入站點包目錄的權限。這在 Windows 上通常不是問題。
導入擴展
安裝擴展后,您將能夠在 Python 腳本中導入和調用該擴展,如下所示 -
#!/usr/bin/python
import helloworld
print helloworld.helloworld()
這將產生以下結果 -
Hello, Python extensions!!
傳遞函數參數
由于您很可能希望定義接受參數的函數,因此您可以為您的 C 函數使用其他簽名之一。例如,接受一些參數的以下函數將被定義如下 -
static PyObject *module_func(PyObject *self, PyObject *args) {
/* Parse args and do something interesting here. */
Py_RETURN_NONE;
}
包含新函數條目的方法表如下所示 -
static PyMethodDef module_methods[] = {
{ "func", (PyCFunction)module_func, METH_NOARGS, NULL },
{ "func", module_func, METH_VARARGS, NULL },
{ NULL, NULL, 0, NULL }
};
您可以使用 API PyArg_ParseTuple函數從傳遞給 C 函數的一個 PyObject 指針中提取參數。
PyArg_ParseTuple 的第一個參數是 args 參數。這是您將要解析的對象。第二個參數是一個格式字符串,描述您希望它們出現的參數。每個參數由格式字符串中的一個或多個字符表示,如下所示。
static PyObject *module_func(PyObject *self, PyObject *args) {
int i;
double d;
char *s;
if (!PyArg_ParseTuple(args, "ids", &i, &d, &s)) {
return NULL;
}
/* Do something interesting here. */
Py_RETURN_NONE;
}
編譯模塊的新版本并導入它使您能夠使用任意數量的任何類型的參數調用新函數 -
module.func(1, s="three", d=2.0)
module.func(i=1, d=2.0, s="three")
module.func(s="three", d=2.0, i=1)
你可能會想出更多的變化。
PyArg_ParseTuple函數_
這是PyArg_ParseTuple函數的標準簽名 -
int PyArg_ParseTuple(PyObject* tuple,char* format,...)
此函數返回 0 表示錯誤,不等于 0 的值表示成功。tuple 是 PyObject*,它是 C 函數的第二個參數。這里的格式是一個描述強制和可選參數的 C 字符串。
這是PyArg_ParseTuple函數的格式代碼列表 -
代碼 C型 意義
C 字符 長度為 1 的 Python 字符串變為 C 字符。
d 雙倍的 Python float 變成了 C double。
F 漂浮 Python 浮點數變成了 C 浮點數。
一世 整數 Python int 變成了 C int。
l 長 Python int 變成了 C long。
大號 長長的 Python int 變成 C long long
○ PyObject* 獲取對 Python 參數的非 NULL 借用引用。
s 字符* 沒有嵌入 C char* 的空值的 Python 字符串。
## 字符*+整數 任何 Python 字符串到 C 地址和長度。
## 字符*+整數 只讀單段緩沖區到 C 地址和長度。
你 Py_UNICODE* Python Unicode 沒有嵌入到 C 中的空值。
你# Py_UNICODE*+int 任何 Python Unicode C 地址和長度。
# 字符*+整數 讀/寫單段緩沖區到 C 地址和長度。
z 字符* 與 s 一樣,也接受 None(將 C char* 設置為 NULL)。
z# 字符*+整數 與 s# 一樣,也接受 None(將 C char* 設置為 NULL)。
(...) 按照 ... Python 序列被視為每個項目的一個參數。
| 以下參數是可選的。
: 格式結束,后跟錯誤消息的函數名稱。
; 格式結束,后跟整個錯誤消息文本。
返回值
Py_BuildValue采用與PyArg_ParseTuple非常相似的格式字符串。不是傳入正在構建的值的地址,而是傳入實際值。這是一個顯示如何實現添加功能的示例 -
static PyObject *foo_add(PyObject *self, PyObject *args) {
int a;
int b;
if (!PyArg_ParseTuple(args, "ii", &a, &b)) {
return NULL;
}
return Py_BuildValue("i", a + b);
}
如果在 Python 中實現,這就是它的樣子 -
def add(a, b):
return (a + b)
您可以從函數中返回兩個值,如下所示,這將在 Python 中使用列表進行捕獲。
static PyObject *foo_add_subtract(PyObject *self, PyObject *args) {
int a;
int b;
if (!PyArg_ParseTuple(args, "ii", &a, &b)) {
return NULL;
}
return Py_BuildValue("ii", a + b, a - b);
}
如果在 Python 中實現,這就是它的樣子 -
def add_subtract(a, b):
return (a + b, a - b)
Py_BuildValue函數_
這是Py_BuildValue函數的標準簽名 -
PyObject* Py_BuildValue(char* format,...)
這里的格式是一個描述要構建的 Python 對象的 C 字符串。Py_BuildValue的以下參數是從中構建結果的 C 值。PyObject *結果是一個新的參考。
下表列出了常用的代碼字符串,其中零個或多個連接成字符串格式。
代碼 C型 意義
C 字符 AC char 變成長度為 1 的 Python 字符串。
d 雙倍的 AC double 成為 Python 浮點數。
F 漂浮 AC 浮點數變成了 Python 浮點數。
一世 整數 AC int 成為 Python int。
l 長 AC long 成為 Python int。
? PyObject* 傳遞一個 Python 對象并竊取一個引用。
○ PyObject* 傳遞一個 Python 對象并像往常一樣對其進行 INCREF。
歐& 轉換+無效* 任意轉換
s 字符* C 以 0 結尾的 char* 到 Python 字符串,或 NULL 到 None。
## 字符*+整數 C char* 和長度為 Python 字符串,或 NULL 為無。
你 Py_UNICODE* C 范圍的、以 null 結尾的字符串到 Python Unicode,或 NULL 到 None。
你# Py_UNICODE*+int C 范圍的字符串和長度到 Python Unicode,或 NULL 到 None。
# 字符*+整數 讀/寫單段緩沖區到 C 地址和長度。
z 字符* 與 s 一樣,也接受 None(將 C char* 設置為 NULL)。
z# 字符*+整數 與 s# 一樣,也接受 None(將 C char* 設置為 NULL)。
(...) 按照 ... 從 C 值構建 Python 元組。
[...] 按照 ... 從 C 值構建 Python 列表。
{...} 按照 ... 從 C 值、交替鍵和值構建 Python 字典。
代碼 {...} 從偶數個 C 值(交替鍵和值)構建字典。例如,Py_BuildValue("{issi}",23,"zig","zag",42) 返回類似于 Python 的 {23:'zig','zag':42} 的字典。