1. 程式人生 > >Python介面(2):使用ctpyes模組在python中呼叫c++程式碼

Python介面(2):使用ctpyes模組在python中呼叫c++程式碼

如何在Python中呼叫C/C++程式碼
How to mix C/C++ code in Python

本文介紹一種手動的、簡單的在Python中使用C/C++程式碼的方式。這個方法主要使用了ctypes模組。其他的混合Python,C/C++程式設計的方法還有Swig 和 Boost.Python。前一種方法需要寫一個介面檔案(interface),而後一種需要使用龐大、深奧的boost類庫,後兩者適合可能適合更復雜的情況,這裡只介紹第一種方法。

混合C/C++程式碼需要這幾步:
1. 包裝介面 C/C++ wrap functions up
2. 打包成共享庫 Compiling C/C++ code and pack it to shared library
3. Python中匯入共享庫 Python imports shared library

先介紹一下背景,這裡我的C++類GenomeSequence使用了模板(Template)和Memorymap,這是一個訪問基因序列的類,比如如果一個生物序列是GAGTTTTATCGCTTCCATGACGCAGAAGTTAACACT… 我們的類是gs,那麼gs[0] = ‘G’, gs[1]=’A’ …. 摘錄相關的函式如下:

class GenomeSequence : public genomeSequenceArray
{
public:
    /// Simple constructor - no implicit file open
    GenomeSequence();
    /// set the reference name that will be used in open()
    /// \param referenceFilename the name of the reference fasta file to open
    /// \return false for success, true otherwise
    ///
    /// \sa open()
    bool setReferenceName(std::string referenceFilename);
    /// return the number of bases represented in this reference
    /// \return count of bases
    genomeIndex_t   getNumberBases() const
    {
        return getElementCount();
    }
    inline char operator[](genomeIndex_t index) const
    {
        uint8_t val;
        if (index < getNumberBases())
        {
            if ((index&1)==0)
            {
                val = ((uint8_t *) data)[index>>1] & 0xf;
            }
            else
            {
                val = (((uint8_t *) data)[index>>1] & 0xf0) >> 4;
            }
        }
        else
        {
            val = baseNIndex;
        }
        val = isColorSpace() ? int2colorSpace[val] : int2base[val];
        return val;
    }
    /* ........... more codes omitted ................ */
}


第二步是編譯,記住單個C/C++檔案編譯時使用-fPIC引數,最後打包的時候編譯成共享庫,摘錄Makefile檔案中片段如下:

lib:
    g++ -c -fPIC -I./lib GenomeSequence_wrap.c
    g++ -shared -Wl,-soname,libstatgen.so -o libstatgen.so  


最後一步是在Python中寫一個封裝類,注意前兩行引入ctypes庫,之後就用這個庫呼叫包裝函式就行。
注意:我在GenomeSequence類的__getitem__中使用了如何擴充套件Python的容器類一文中介紹的一些技巧,這樣可以更靈活的使用下標來訪問陣列中的元素。

fromctypes importcdll
lib=cdll.LoadLibrary("./libstatgen.so")
 
classGenomeSequence:
    def__init__ (self):
        self.obj=lib.GenomeSequence_new()
    defopen(self, filename):
        lib.GenomeSequence_setReferenceName(self.obj, filename)
    def__len__ (self):
        returnlib.GenomeSequence_getNumBase(self.obj)
    def__getitem__(self, key):
        ifisinstance(key,int):
            returnchr(lib.GenomeSequence_getBase(self.obj, key))
        elifisinstance(key,slice):
            return''.join([self[x]forx inxrange(*key.indices(len(self)))])
        elifisinstance(key,tuple):
            return''.join([self[i]fori inkey])
 
    defat(self, i):
        returnchr(lib.GenomeSequence_getBase(self.obj, i))
    defclose(self):
        lib.GenomeSequence_close(self.obj)
     
if__name__ =='__main__':
    gs=GenomeSequence ()
    gs.open("/home/zhanxw/statgen/src/karma/test/phiX.fa");
    printlen(gs)
    seq=[(gs.at(i)) fori inxrange(60)]
    print''.join(seq)
    printgs[0:10],gs[20:30]
    printgs[0:10,20:30]
    printgs[-10:]
    gs.close()
    print"DONE"


本文主要參考【1】。這裡的方法基本重複了【1】中的步驟。寫出本文中的程式碼在於進一步驗證ctypes庫可以靈活的處理C/C++和Python中的簡單資料型別int, char*。