python擴充實現方法--python與c混和編程

來源:互聯網
上載者:User

標籤:bsp   是你   blog   語言   檔案中   數列   archive   瓶頸   src   

Reference: http://www.cnblogs.com/btchenguang/archive/2012/09/04/2670849.html

 

python 標頭檔在的位置:/usr/include/python2.7

                                 /usr/local/include/python2.7

 

 

前言(更新:更方便易用的方式在http://www.swig.org/tutorial.html)大部分的Python的擴充都是用C語言寫的,但也很容易移植到C++中。一般來說,所有能被整合或者匯入到其它python指令碼的代碼,都可以稱為擴充。擴充可以用純Python來寫,也可以用C或者C++之類的編譯型的語言來擴充。 就算是相同的架構的兩台電腦之間最好也不要互相共用二進位檔案,最好在各自的電腦上編譯Python和擴充。因為就算是編譯器或者CPU之間的些許差異。 官方文檔http://docs.python.org/extending/windows.html  需要擴充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;
}一般是需要寫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;
}也可以寫成更簡短,可讀性更強的形式: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));
}下面是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));
}也可以再改造成返回包含原始字串和反轉字串的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)));
}上面的代碼有什麼問題呢?和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;
} 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},
};METH_VARARGS代表參數以tuple的形式傳入。如果我們需要使用PyArg_ParseTupleAndKeywords()函數來分析關鍵字參數的話,這個標誌常量應該寫成: METH_VARARGS & METH_KEYWORDS,進行邏輯與運算。  d. 增加模組初始化函數void initMethod()最後的工作就是模組的初始化工作。這部分代碼在模組被python匯入時進行調用。void initExtest() {
    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;
}  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‘])])setup函數還有很多選項可以設定。詳情可見官網。http://docs.python.org/distutils/setupscript.html  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,我下載一個相同版本的源碼,也可以下載更高版本。http://www.python.org/download/releases/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) 也可以在目前的目錄下執行命令,安裝到你的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("");
}簡單效能比較測試代碼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運行結果可以看出,python也不是絕對比C慢嘛,還要看情況。

python擴充實現方法--python與c混和編程

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.