前言(更新:更方便易用的方式在http://www.swig.org/tutorial.html)
大部分的Python的擴展都是用C語言寫的,但也很容易移植到C++中。
一般來說,所有能被整合或者導入到其它python腳本的代碼,都可以稱爲擴展。
擴展可以用純Python來寫,也可以用C或者C++之類的編譯型的語言來擴展。
就算是相同的架構的兩臺電腦之間最好也不要互相共享二進制文件,最好在各自的
電腦上編譯Python和擴展。因爲就算是編譯器或者CPU之間的些許差異。
官方文檔
需要擴展Python語言的理由:
1. 添加/額外的(非Python)功能,提供Python核心功能中沒有提供的部分,比如創建新的
數據類型或者將Python嵌入到其它已經存在的應用程序中,則必須編譯。
2. 性能瓶頸的效率提升, 解釋型語言一般比編譯型語言慢,想要提高性能,全部改寫成編譯型
語言並不划算,好的做法是,先做性能測試,找出性能瓶頸部分,然後把瓶頸部分在擴展中實現,
是一個比較簡單有效的做法。
3. 保持專有源代碼的私密,腳本語言一個共同的缺陷是,都是執行的源代碼,保密性便沒有了。
把一部分的代碼從Python轉到編譯語言就可以保持專有源代碼私密性。不容易被反向工程,對涉及
到特殊算法,加密方法,以及軟件安全時,這樣做就顯得很重要。
另一種對代碼保密的方式是隻發佈預編譯後的.pyc文件,是一種折中的方法。
創建Python擴展的步驟
1. 創建應用程序代碼
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define BUFSIZE 10
int fac(int n) {
if (n < 2)
return 1;
return n * fac(n - 1);
}
char *reverse(char *s) {
register char t;
char *p = s;
char *q = (s + (strlen(s) - 1));
while (p < q) {
t = *p;
*p++ = *q;
*q-- = t;
}
return s;
}
int main() {
char s[BUFSIZE];
printf("4! == %d\n", fac(4));
printf("8! == %d\n", fac(8));
printf("12! == %d\n", fac(12));
strcpy(s, "abcdef");
printf("reversing 'abcdef', we get '%s'\n", reverse(s));
strcpy(s, "madam");
printf("reversing 'madam', we get '%s'\n", reverse(s));
return 0;
}
#include <stdlib.h>
#include <string.h>
#define BUFSIZE 10
int fac(int n) {
if (n < 2)
return 1;
return n * fac(n - 1);
}
char *reverse(char *s) {
register char t;
char *p = s;
char *q = (s + (strlen(s) - 1));
while (p < q) {
t = *p;
*p++ = *q;
*q-- = t;
}
return s;
}
int main() {
char s[BUFSIZE];
printf("4! == %d\n", fac(4));
printf("8! == %d\n", fac(8));
printf("12! == %d\n", fac(12));
strcpy(s, "abcdef");
printf("reversing 'abcdef', we get '%s'\n", reverse(s));
strcpy(s, "madam");
printf("reversing 'madam', we get '%s'\n", reverse(s));
return 0;
}
一般是需要寫main()函數,用於單元測試
使用gcc進行編譯
>gcc Extest.c -o Extest
執行
>./Extest
2. 利用樣板來包裝代碼
整個擴展的實現都是圍繞"包裝"這個概念來進行的。你的設計要儘可能讓你的實現語言與Python無縫結合。
接口的代碼又被稱爲"樣板"代碼,它是你的代碼與Python解釋器之間進行交互所必不可少的部分:
我們的樣板代碼分爲4步:
a. 包含python的頭文件
需要找到python的頭文件在哪,一般是在/usr/local/include/python2.x中
在上面的C代碼中加入#include "Python.h"
b. 爲每個模塊的每一個函數增加一個型如PyObject* Module_func()的包裝函數
包裝函數的用處就是先把python的值傳遞給c,再把c中函數的計算結果轉換成Python對象返回給python。
需要爲所有想被Python環境訪問到的函數都增加一個靜態函數,返回類型爲PyObject *,函數名格式爲
模塊名_函數名;
static PyObject * Extest_fac(PyObject *self,
PyObject *args) {
int res;//計算結果值
int num;//參數
PyObject* retval;//返回值
//i表示需要傳遞進來的參數類型爲整型,如果是,就賦值給num,如果不是,返回NULL;
res = PyArg_ParseTuple(args, "i", &num);
if (!res) {
//包裝函數返回NULL,就會在Python調用中產生一個TypeError的異常
return NULL;
}
res = fac(num);
//需要把c中計算的結果轉成python對象,i代表整數對象類型。
retval = (PyObject *)Py_BuildValue("i", res);
return retval;
}
int res;//計算結果值
int num;//參數
PyObject* retval;//返回值
//i表示需要傳遞進來的參數類型爲整型,如果是,就賦值給num,如果不是,返回NULL;
res = PyArg_ParseTuple(args, "i", &num);
if (!res) {
//包裝函數返回NULL,就會在Python調用中產生一個TypeError的異常
return NULL;
}
res = fac(num);
//需要把c中計算的結果轉成python對象,i代表整數對象類型。
retval = (PyObject *)Py_BuildValue("i", res);
return retval;
}
也可以寫成更簡短,可讀性更強的形式:
static PyObject * Extest_fac(PyObject *self,
PyObject *args) {
int m;
if (!(PyArg_ParseTuple(args, "i", &num))) {
return NULL;
}
return (PyObject *)Py_BuildValue("i", fac(num));
}
int m;
if (!(PyArg_ParseTuple(args, "i", &num))) {
return NULL;
}
return (PyObject *)Py_BuildValue("i", fac(num));
}
下面是python和c對應的類型轉換參數表:
這裏還有一個Py_BuildValue的用法表:
reverse函數的包裝也類似:
static PyObject *
Extest_reverse(PyObject *self, PyObject *args) {
char *orignal;
if (!(PyArg_ParseTuple(args, "s", &orignal))) {
return NULL;
}
return (PyObject *)Py_BuildValue("s", reverse(orignal));
}
Extest_reverse(PyObject *self, PyObject *args) {
char *orignal;
if (!(PyArg_ParseTuple(args, "s", &orignal))) {
return NULL;
}
return (PyObject *)Py_BuildValue("s", reverse(orignal));
}
也可以再改造成返回包含原始字串和反轉字串的tuple的函數
static PyObject *
Extest_doppel(PyObject *self, PyObject *args) {
char *orignal;
if (!(PyArg_ParseTuple(args, "s", &orignal))) {
return NULL;
}
//ss,就可以返回兩個字符串,應該reverse是在原字符串上進行操作,所以需要先strdup複製一下
return (PyObject *)Py_BuildValue("ss", orignal, reverse(strdup(orignal)));
}
Extest_doppel(PyObject *self, PyObject *args) {
char *orignal;
if (!(PyArg_ParseTuple(args, "s", &orignal))) {
return NULL;
}
//ss,就可以返回兩個字符串,應該reverse是在原字符串上進行操作,所以需要先strdup複製一下
return (PyObject *)Py_BuildValue("ss", orignal, reverse(strdup(orignal)));
}
上面的代碼有什麼問題呢?
和c語言相關的問題,比較常見的就是內存泄露。。。上面的例子中,Py_BuildValue()函數生成
要返回Python對象的時候,會把轉入的數據複製一份。上面的兩個字符串都被複製出來。但是
我們申請了用於存放第二個字符串的內存,在退出的時候沒有釋放掉它。於是內存就泄露了。
正確的做法是:先生成返回的python對象,然後釋放在包裝函數中申請的內存。
static PyObject *
Extest_doppel(PyObject *self, PyObject *args) {
char *orignal;
char *reversed;
PyObject * retval;
if (!(PyArg_ParseTuple(args, "s", &orignal))) {
return NULL;
}
retval = (PyObject *)Py_BuildValue("ss", orignal, reversed=reverse(strdup(orignal)));
free(reversed);
return retval;
}
Extest_doppel(PyObject *self, PyObject *args) {
char *orignal;
char *reversed;
PyObject * retval;
if (!(PyArg_ParseTuple(args, "s", &orignal))) {
return NULL;
}
retval = (PyObject *)Py_BuildValue("ss", orignal, reversed=reverse(strdup(orignal)));
free(reversed);
return retval;
}
c. 爲每個模塊增加一個型如PyMethodDef ModuleMethods[]的數組
我們已經創建了幾個包裝函數,需要在某個地方把它們列出來,以便python解釋器能夠導入並調用它們。
這個就是ModuleMethods[]數組所需要做的事情。
格式如下 ,每一個數組都包含一個函數的信息,最後一個數組放置兩個NULL值,代表聲明結束
static PyMethodDef
ExtestMethods[] = {
{"fac", Extest_fac, METH_VARARGS},
{"doppel", Extest_doppel, METH_VARARGS},
{"reverse", Extest_reverse, METH_VARARGS},
{NULL, NULL},
};
ExtestMethods[] = {
{"fac", Extest_fac, METH_VARARGS},
{"doppel", Extest_doppel, METH_VARARGS},
{"reverse", Extest_reverse, METH_VARARGS},
{NULL, NULL},
};
METH_VARARGS代表參數以tuple的形式傳入。如果我們需要使用PyArg_ParseTupleAndKeywords()
函數來分析關鍵字參數的話,這個標誌常量應該寫成: METH_VARARGS & METH_KEYWORDS,進行邏輯與運算。
d. 增加模塊初始化函數void initMethod()
最後的工作就是模塊的初始化工作。這部分代碼在模塊被python導入時進行調用。
void initExtest() {
Py_InitModule("Extest", ExtestMethods);
}
Py_InitModule("Extest", ExtestMethods);
}
最終代碼如下:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "Python.h"
#define BUFSIZE 10
int fac(int n) {
if (n < 2)
return 1;
return n * fac(n - 1);
}
char *reverse(char *s) {
register char t;
char *p = s;
char *q = (s + (strlen(s) - 1));
while (p < q) {
t = *p;
*p++ = *q;
*q-- = t;
}
return s;
}
static PyObject *
Extest_fac(PyObject *self, PyObject *args) {
int res;
int num;
PyObject* retval;
res = PyArg_ParseTuple(args, "i", &num);
if (!res) {
return NULL;
}
res = fac(num);
retval = (PyObject *)Py_BuildValue("i", res);
return retval;
}
static PyObject *
Extest_reverse(PyObject *self, PyObject *args) {
char *orignal;
if (!(PyArg_ParseTuple(args, "s", &orignal))) {
return NULL;
}
return (PyObject *)Py_BuildValue("s", reverse(orignal));
}
static PyObject *
Extest_doppel(PyObject *self, PyObject *args) {
char *orignal;
char *resv;
PyObject *retval;
if (!(PyArg_ParseTuple(args, "s", &orignal))) {
return NULL;
}
retval = (PyObject *)Py_BuildValue("ss", orignal, resv=reverse(strdup(orignal)));
free(resv);
return retval;
}
static PyMethodDef
ExtestMethods[] = {
{"fac", Extest_fac, METH_VARARGS},
{"doppel", Extest_doppel, METH_VARARGS},
{"reverse", Extest_reverse, METH_VARARGS},
{NULL, NULL},
};
void initExtest() {
Py_InitModule("Extest", ExtestMethods);
}
int main() {
char s[BUFSIZE];
printf("4! == %d\n", fac(4));
printf("8! == %d\n", fac(8));
printf("12! == %d\n", fac(12));
strcpy(s, "abcdef");
printf("reversing 'abcdef', we get '%s'\n", reverse(s));
strcpy(s, "madam");
printf("reversing 'madam', we get '%s'\n", reverse(s));
test();
return 0;
}
#include <stdlib.h>
#include <string.h>
#include "Python.h"
#define BUFSIZE 10
int fac(int n) {
if (n < 2)
return 1;
return n * fac(n - 1);
}
char *reverse(char *s) {
register char t;
char *p = s;
char *q = (s + (strlen(s) - 1));
while (p < q) {
t = *p;
*p++ = *q;
*q-- = t;
}
return s;
}
static PyObject *
Extest_fac(PyObject *self, PyObject *args) {
int res;
int num;
PyObject* retval;
res = PyArg_ParseTuple(args, "i", &num);
if (!res) {
return NULL;
}
res = fac(num);
retval = (PyObject *)Py_BuildValue("i", res);
return retval;
}
static PyObject *
Extest_reverse(PyObject *self, PyObject *args) {
char *orignal;
if (!(PyArg_ParseTuple(args, "s", &orignal))) {
return NULL;
}
return (PyObject *)Py_BuildValue("s", reverse(orignal));
}
static PyObject *
Extest_doppel(PyObject *self, PyObject *args) {
char *orignal;
char *resv;
PyObject *retval;
if (!(PyArg_ParseTuple(args, "s", &orignal))) {
return NULL;
}
retval = (PyObject *)Py_BuildValue("ss", orignal, resv=reverse(strdup(orignal)));
free(resv);
return retval;
}
static PyMethodDef
ExtestMethods[] = {
{"fac", Extest_fac, METH_VARARGS},
{"doppel", Extest_doppel, METH_VARARGS},
{"reverse", Extest_reverse, METH_VARARGS},
{NULL, NULL},
};
void initExtest() {
Py_InitModule("Extest", ExtestMethods);
}
int main() {
char s[BUFSIZE];
printf("4! == %d\n", fac(4));
printf("8! == %d\n", fac(8));
printf("12! == %d\n", fac(12));
strcpy(s, "abcdef");
printf("reversing 'abcdef', we get '%s'\n", reverse(s));
strcpy(s, "madam");
printf("reversing 'madam', we get '%s'\n", reverse(s));
test();
return 0;
}
3. 編譯與測試
爲了讓你的新python擴展能夠被創建,你需要把它們與python庫放在一起編譯。python中的distutils包被
用來編譯,安裝和分發這些模塊,擴展和包。步驟如下:
a. 創建setup.py
我們在安裝python第三方包的時候,很多情況下會用到python setup.py install這個命令,
下面我們來了解一下setup.py文件的內容。
編譯的最主要的內容由setup函數完成,你需要爲每一個擴展創建一個Extension實例,在這裏我們只有一個
擴展,所以只需要創建一個實例。
Extension('Extest', sources=['Extest.c']),第一個參數是擴展的名字,如果模塊是包的一部分,還需要加".";
第二個參數是源代碼文件列表
setup('Extest', ext_modules=[...]),第一個參數表示要編譯哪個東西,第二個參數列出要編譯的Extension對象。
#!/usr/bin/env python
from distutils.core import setup, Extension
MOD = 'Extest'
setup(name=MOD, ext_modules=[Extension(MOD, sources['Extest.c'])])
from distutils.core import setup, Extension
MOD = 'Extest'
setup(name=MOD, ext_modules=[Extension(MOD, sources['Extest.c'])])
setup函數還有很多選項可以設置。詳情可見官網。
b. 通過運行setup.py來編譯和連接你的代碼
在shell中運行命令
>python setup.py build
當你報錯如:無法找到Python.h文件
那麼說明你沒有安裝python-dev包,需要去官網下載源碼包重裝自己編譯安裝一下python。
Python.h文件一般會出現在/usr/include/Python2.X文件夾中,我這裏反正是沒有的。。。
只有重新編譯一個python...
我現在linux系統上的python版本是2.6.6,我下載一個相同版本的源碼,也可以下載更高版本。
解壓源碼包
> tar xzf Python-2.6.6.tgz
> cd Python-2.6.6.tgz
編譯安裝Python
> ./configure --prefix=/usr/local/python2.6
> make
> sudo make install
創建一個新編譯python的鏈接
> sudo ln -sf /usr/local/python2.6/bin/python2.6 /usr/bin/python2.6
測試一下,可用
使用這種方法可以在Linux上運行不同版本的python.
Python.h文件也在/usr/local/python2.6/include/python2.6路徑下找到。
重新運行編譯
編譯成功後,你的擴展就會被創建在bulid/lib.*目錄下。你會看到一個.so文件,這是linux下的
動態庫文件:
c. 進行調試
你可以直接用python代碼調用進行測試:
#!/usr/bin/python
from ctypes import *
import os
#需要使用絕對路徑
extest = cdll.LoadLibrary(os.getcwd() + '/Extest.so')
print extest.fac(4)
from ctypes import *
import os
#需要使用絕對路徑
extest = cdll.LoadLibrary(os.getcwd() + '/Extest.so')
print extest.fac(4)
也可以在當前目錄下執行命令,安裝到你的python路徑下
> python setup.py install
安裝成功的話,直接導入測試:
最後需要注意一點的是,原來的c文件中有一個main函數,因爲一個系統中只能有一個main
函數,所以爲了不起衝突,可以把main函數改成test函數,再用Extest_test()包裝函數處理一下,
再加入ExtestMethods數組,這樣就可以調用這個測試函數了。
static PyObject *
Extest_test(PyObject *self, PyObject *args) {
test();
#返回空的話,就使用下面這一句
return (PyObject *)Py_BuildValue("");
}
Extest_test(PyObject *self, PyObject *args) {
test();
#返回空的話,就使用下面這一句
return (PyObject *)Py_BuildValue("");
}
簡單性能比較
測試代碼
import Extest
import time
start = time.time()
a = Extest.reverse("abcd")
timeC = time.time() - start
print 'C costs', timeC, 'the result is', a
start = time.time()
b = list("abcd")
b.reverse()
b = ''.join(b)
timePython = time.time()-start
print 'Python costs', timePython, 'the result is', b
import time
start = time.time()
a = Extest.reverse("abcd")
timeC = time.time() - start
print 'C costs', timeC, 'the result is', a
start = time.time()
b = list("abcd")
b.reverse()
b = ''.join(b)
timePython = time.time()-start
print 'Python costs', timePython, 'the result is', b
運行結果
可以看出,python也不是絕對比C慢嘛,還要看情況。