boost.python筆記

boost.python筆記

標簽:boost.python


簡介

Boost.python是什么尘惧?
它是boost庫的一部分,隨boost一起安裝含衔,用來實現C++和Python代碼的交互股耽。

使用Boost.python有什么特點颜曾?
不需要修改原有的C++代碼泽示,支持比較豐富的C++特性缸血。不會生成額外的python代碼(像SWIG那樣),但是需要寫一部分C++的封裝代碼械筛。

我只用到了其功能的一部分捎泻,把C/C++實現的功能封裝為可供python直接調用的.so庫。具體場景是变姨,有一個C++模塊通過thrift封裝為RPC族扰,python代碼通過PRC調用請求服務。由于調用頻次較多定欧,RPC調用開銷成為一個很耗時的部分,因此想直接通過python對原模塊功能進行調用怒竿。

以前了解SWIG可以實現這個需求砍鸠。本已開始看SWIG的文檔,但突然又想看一下還有沒有別的方法耕驰,于是在stack overflow上搜了一些問題爷辱,發(fā)現不少人推薦Boost.python,于是打算拿它來試一試。

如何使用Boost.python

首先饭弓,前提是安裝了開發(fā)環(huán)境双饥。

  1. 安裝了boost開發(fā)環(huán)境。安裝了頭文件和動態(tài)庫弟断。
  2. 安裝了python開發(fā)環(huán)境咏花。安裝了頭文件和動態(tài)庫。

然后阀趴,就是寫代碼了昏翰,這是個沒辦法避免的事情!A跫薄棚菊!

需要自己動手的有兩個地方。一個是xxxxxx_wrapper.cpp文件叔汁,文件名無所謂统求,其核心目的是定義導出的python模塊的名稱,以及需要導出的類据块、函數等码邻。另一個是需要修改一下你的Makefile,來編譯瑰钮、鏈接這個so庫冒滩。

先來看一下xxxxxx_wrapper.cpp。一般情況下浪谴,它的內容跟下面的代碼比較接近开睡。

#include <boost/python.hpp>
// 其他需要包含的頭文件,與具體業(yè)務有關

namespace py = boost::python;

// 其他函數苟耻,可能包括一些用于類型轉換和封裝的

BOOST_PYTHON_MODULE(my_module_name)
{
    // 導出普通函數
    def("fun_name_in_python", &fun_name_in_c);
    
    // 導出類及部分成員
    class_<ClassNameInCpp>("ClassNameInPython", init<std::string>())               //類名篇恒,默認構造函數
        .def(init<double>())                                                       //其他構造函數
        .def("memberFunNameInPython", &ClassNameInCpp::memberFunNameInCpp)         //成員函數
        .def_readwrite("dataMemberInPython", &ClassNameInCpp::dataMemberInCpp)     //普通成員變量
        .def_readonly("dataMemberInPython_2", &ClassNameInCpp::dataMemberInCpp_2)  //只讀成員變量
    ;
}

這個文件的核心目的體現在BOOST_PYTHON_MODULE里,定義需要導出給python的東西凶杖。

在Makefile里胁艰,需要增加一條用來編譯導出的.so的規(guī)則,編譯命令里通用的部分一般像下面這樣智蝠,這里把編譯和鏈接寫在一起了腾么。

g++ -o my_module_name.so -shared -fPIC -I${BOOST_INCLUDE_PATH} -I${PYTHON_INCLUDE_PATH} -L${BOOST_LIB_DIR} -lboost_python ${MY_SRC_FILES}

編譯及鏈接參數的作用如下,其他參數由具體項目的業(yè)務邏輯決定:

  1. -o my_module_name.so杈湾,這里的模塊名需要和xxxxxx_wrapper.cpp文件里BOOST_PYTHON_MODULE(my_module_name)一致解虱;
  2. -I${BOOST_INCLUDE_PATH} -I${PYTHON_INCLUDE_PATH}是編譯需要的;
  3. -L${BOOST_LIB_DIR} -lboost_python -shared -fPIC是鏈接需要的漆撞;
  4. ${MY_SRC_FILES}包含了xxxxxxx_wrapper.cpp以及業(yè)務邏輯需要的其他.cpp殴泰,.c文件于宙;

至此,我們便有了my_module_name.so這個可以被python調用的模塊了悍汛。測試一下吧捞魁。

>>>import my_moduel_name
>>>help(my_module_name)

可以看到被導出的類及函數,然后可以按照python的習慣來使用這些類和函數了离咐。

如何寫wrapper

以一個實例為框架來解釋吧谱俭,內容包括普通函數、類健霹、數據成員旺上、成員函數、通過參數傳遞結果糖埋、容器宣吱。其他特性沒有用到,也沒有測試瞳别。

先來看一下業(yè)務邏輯的代碼征候。包含一個類,一個以類對象為參數的函數祟敛,一個通過引用修改類對象的函數疤坝。

//test_class.h

// 定義一個類
class A
{
    public:
        A(){privateVal=0;}                      //默認構造函數
        A(int val){privateVal=val;}             //帶參數的構造函數
        void set(int val){privateVal=val;}      //成員函數
        int get() const {return privateVal;};   //成員函數
        int publicVal;                          //公共數據成員
    private:
        int privateVal;                         //私有數據成員
};

int addA(A &a, int addVal);                   //普通函數,有返回值馆铁,通過引用修改參數
void printA(const A& a);   
//test_class.cpp
#include <stdio.h>
#include "test_class.h"

int addA(A &a, int addVal)
{
    int val = a.get();
    val += addVal;
    a.set(val);
    return val;
}
void printA(const A& a)
{
    printf("%d\n", a.get());
}

然后是wrapper.cpp文件跑揉,這里實際名為test_class_wrapper.cpp。

//test_class_wrapper.cpp
#include <boost/python.hpp>
#include "test_class.h"

BOOST_PYTHON_MODULE(test_class)
{
    using namespace boost::python;
    // 導出類
    class_<A>("A", init<>())                            //如果默認構造函數沒有參數埠巨,可以省略
        .def(init<int>())                               //其他構造函數
        .def("get", &A::get)                            //成員函數
        .def("set", &A::set)                            //成員函數
        .def_readwrite("publicVal", &A::publicVal)      //數據成員历谍,當然是公共的
    ;   
    def("printA", &printA);
    def("addA", &addA);
}

通過python命令行測試一下

>>>import test_class
>>>a = test_class.A(5)
>>>ret = addA(a, 10)
>>>print ret
15
>>>print a.get()
15

到目前為止,整個過程都很順利辣垒。需要額外寫的代碼很少望侈,也很規(guī)整,與某種IDL的寫法接近勋桶,只需要“聲明”一下脱衙,剩下的事情都交給編譯器及庫完成。但有的時候例驹,這個過程就不這么順利了捐韩,我們需要額外寫一些轉換及封裝。比如在這一部分最開始提到的容器鹃锈,上面的代碼就沒有涉及奥帘。

下面的代碼,我們對上面的例子做了一些擴展仪召。第一寨蹋,對類A增加了一個vector成員,需要在python代碼里引用該成員扔茅;第二已旧,增加了一個函數,以vector為參數召娜,需要在python代碼里直接調用該函數运褪。下面我們就來解釋與容器有關的導出。

#include<vector>

class B;

class A
{
    public:
        A(){privateVal=0;}                      //默認構造函數
        A(int val){privateVal=val;}             //帶參數的構造函數
        void set(int val){privateVal=val;}      //成員函數
        int get() const {return privateVal;};   //成員函數
        int publicVal;                          //公共數據成員
        std::vector<B> m_vB;
    private:
        int privateVal;                           //私有數據成員
};

class B
{
    public:
        B(){}
        ~B(){}
        int pos;
        int len;
};

int accumulate(const std::vector<A>& v_A);
int addA(A &a, int addVal);                   //普通函數玖瘸,有返回值秸讹,通過引用修改參數
void printA(const A& a);   
#include <stdio.h>
#include "test_class.h"

int addA(A &a, int addVal)
{
    int val = a.get();
    val += addVal;
    a.set(val);
    return val;
}
void printA(const A& a)
{
    printf("%d\n", a.get());
}

int accumulate(const std::vector<A>& v_A)
{
    int ret = 0;
    for (size_t i = 0; i < v_A.size(); i++)
    {   
        ret += v_A[i].get();
    }   
    return ret;
}

首先,需要明白一點雅倒,c++中的vector不等于python中的list璃诀,雖然它們看上去比較相似。Boost.python中有與python的list對應的東西蔑匣,是boost::python::list劣欢,如果在python代碼里以list為參數調用某個方法,則在c++代碼中這個參數被自動映射為boost::python::list裁良,不是vector凿将。既然這樣,如果我們不打算修改原有的C++代碼价脾,又想調用以vector為參數的函數牧抵,該怎么辦呢?

目前我了解的方法由兩種:

  1. 在C++代碼里對以vector為參數的函數進行一層封裝侨把,封裝為以boost::python::list為參數的函數犀变,導出封裝后的函數。在函數里通過boost::python::extract_<T>對list里的所有成員進行提取座硕,將其由boost::python::object對象變?yōu)門類型的對象弛作,然后存于vector<T>中,再調用以vector<T>為參數的函數华匾。
    如果需要返回list或者原函數對vector參數的內容作了修改映琳,需要再將調用函數后的vector內的每個元素放回list里。
  2. 直接導出vector<T>類型蜘拉。此時vector<T>本身作為一個類型被導出給python代碼萨西,與普通的類具有同等地位。但是旭旭,與普通類不同的是谎脯,它通過模板vector_indexing_suite<std::vector<T> >()導出,自動實現了append持寄,slice源梭,__len__等方法娱俺,在python里可以像使用list那樣操作這個被導出的vector類。而且废麻,以vector<T>為參數的函數荠卷,在通過def導出給python時,其參數會被自動映射為vector_indexing_suite<std::vector<T> >烛愧。同理油宜,python代碼傳入的通過vector_indexing_suite導出的容器對象,也會在c++代碼里被自動轉換為vector怜姿,這里無需顯式地寫轉換函數慎冤。

下面的代碼是wrapper文件,使用第二種方法沧卢,即直接導出vector類型蚁堤。

為什么這么做呢?因為A里有個vector成員搏恤,要導出這個成員违寿,必須導出這個vector<B>這個類型,否則還需要對類A再做一層封裝熟空,讓它包含一個boost::python::list成員藤巢,這就太麻煩了。

#include <boost/python.hpp>
#include <boost/python/suite/indexing/vector_indexing_suite.hpp>
#include "test_class.h"

bool operator==(const B& left, const B& right);

bool operator==(const A& left, const A& right)
{
    if (left.get() != right.get() || left.publicVal != right.publicVal)
        return false;
    if (left.m_vB.size() != right.m_vB.size())
        return false;
    for (size_t i = 0; i < left.m_vB.size(); i ++) 
    {   
        if (!(left.m_vB[i] == right.m_vB[i]))
            return false;
    }   
    return true;
}

bool operator==(const B& left, const B& right)
{
    return (left.pos == right.pos && left.len == right.len);
}

BOOST_PYTHON_MODULE(test_class)
{
    using namespace boost::python;
    class_<A>("A", init<>())                            //如果默認構造函數沒有參數息罗,可以省略
        .def(init<int>())                               //其他構造函數
        .def("get", &A::get)                            //成員函數
        .def("set", &A::set)                            //成員函數
        .def_readwrite("publicVal", &A::publicVal)      //數據成員掂咒,當然是公共的
        .def_readwrite("vB", &A::m_vB)
        ;   
    class_<std::vector<A> >("VecA")
        .def(vector_indexing_suite<std::vector<A> >())
        ;   
    class_<B>("B")
        .def_readwrite("pos", &B::pos)
        .def_readwrite("len", &B::len)
        ;   
    class_<std::vector<B> >("VecB")
        .def(vector_indexing_suite<std::vector<B> >())
        ;   
    def("printA", &printA);
    def("addA", &addA);
    def("accumulate", &accumulate);
}

我們還是從BOOST_PYTHON_MODULE內的代碼開始看。

  • class_<A>的定義看上去和前面的例子沒有太大差別迈喉,只是多導出了一個成員.def_readwrite("vB", &A::m_vB)绍刮。即使這個成員變量是vector<B>類型的,在這里也不需要特殊對待挨摸;
  • class_<B>就是導出一個類孩革,包含兩個共有數據成員。這里也沒什么特別的得运;
  • class_<std::vector<A> >("VecA")class_<std::vector<B> >("VecB")是本例的重點膝蜈,導出了兩個不同的vector類型,因為在c++里熔掺,vector是一個類模板饱搏,vector<A>vector<B>才是兩個具體的類型;
  • printA置逻,addA推沸,accumulate是三個導出的函數。即使其參數是vector<A>類型,也無需特別對待鬓催;

除此之外肺素,注意到為類型A和類型B定義了==操作符,這是boost.python在導出某種類型的vector時需要的深浮,在內部某個地方用到了==操作符压怠。如果僅導出類型,不導出類型的向量飞苇,是不需要==操作符的,如前面的例子所示蜗顽。

編譯鏈接后布卡,通過python命令行測試一下:

>>>import test_class
>>>a1 = test_class.A()
>>>b1 = test_class.B()    # 實例化一個B
>>>b1.pos = 1
>>>b1.len = 1
>>>b2 = test_class.B()    # 實例化另一個B
>>>b2.pos = 2
>>>b2.len = 2
>>>a1.vB.append(b1)      # a1.vB是vector<B>在python中對應類型的對象,接口類似list雇盖,但只能添加B類型的對象
>>>a1.vB.append(b2)
>>>print a1.vB[-1].len        # a1.vB支持list的下標引用
2
>>>a1.set(1)
>>>a2 = test_class.A(2)
>>>a3 = test_class.A(3)
>>>vA = test_class.VecA()    # vector<A>在python中對應的類型
>>>vA.append(a1)
>>>vA.append(a2)
>>>vA.append(a3)
>>>print accumulate(vA)      # 調用以vector<A>為參數的函數
6

對于導出的python模塊來說忿等,一切在python中會被引用到的變量,其所屬類型(基本數據類型除外)都需要被明確導出崔挖,也就是都需要在BOOST_PYTHON_MODULE里被定義贸街。如本例中的vector<B>,盡管沒有函數以該類型為參數狸相,但如果想要在python代碼里引用A的成員vB薛匪,就需要導出它,否則會拋異常脓鹃。相反逸尖,如果不需要在python代碼里引用這個成員,則不需要導出vector<B>這個類型瘸右,而且在class_<A>的定義中也應把.def_readwrite("vB", &A::m_vB)去掉娇跟。如果不導出vector<B>類型,但在class_<A>的定義中通過.def_readwrite("vB", &A::m_vB)導出了該成員太颤,編譯不會出問題苞俘,使用python模塊也不會出問題,但只要代碼引用到A.vB就會拋異常龄章,相當于埋了一個坑吃谣。

從實用的角度看,這樣一個流程可能會比較有效瓦堵。首先基协,確定需要導出的函數及類型。然后檢查函數(包括成員函數)參數及返回值的類型菇用,非基本類型需要被導出澜驮;檢查導出的類成員變量,如果不是基本類型惋鸥,其類型也要導出杂穷。如此直到沒有新的類型需要被添加為止悍缠。

總結

Boost.python的文檔感覺比較少,很多問題和trick都是在stack overflow上看到然后再試驗的耐量。據了解飞蚓,Boost.python支持更為豐富的c++特性,這里只用到了一小部分廊蜒。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末趴拧,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子山叮,更是在濱河造成了極大的恐慌著榴,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,039評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件屁倔,死亡現場離奇詭異脑又,居然都是意外死亡,警方通過查閱死者的電腦和手機锐借,發(fā)現死者居然都...
    沈念sama閱讀 93,426評論 3 395
  • 文/潘曉璐 我一進店門问麸,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人钞翔,你說我怎么就攤上這事严卖。” “怎么了嗅战?”我有些...
    開封第一講書人閱讀 165,417評論 0 356
  • 文/不壞的土叔 我叫張陵妄田,是天一觀的道長。 經常有香客問我驮捍,道長疟呐,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,868評論 1 295
  • 正文 為了忘掉前任东且,我火速辦了婚禮启具,結果婚禮上,老公的妹妹穿的比我還像新娘珊泳。我一直安慰自己鲁冯,他們只是感情好,可當我...
    茶點故事閱讀 67,892評論 6 392
  • 文/花漫 我一把揭開白布色查。 她就那樣靜靜地躺著薯演,像睡著了一般。 火紅的嫁衣襯著肌膚如雪秧了。 梳的紋絲不亂的頭發(fā)上跨扮,一...
    開封第一講書人閱讀 51,692評論 1 305
  • 那天,我揣著相機與錄音,去河邊找鬼衡创。 笑死帝嗡,一個胖子當著我的面吹牛,可吹牛的內容都是我干的璃氢。 我是一名探鬼主播哟玷,決...
    沈念sama閱讀 40,416評論 3 419
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼一也!你這毒婦竟也來了巢寡?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,326評論 0 276
  • 序言:老撾萬榮一對情侶失蹤塘秦,失蹤者是張志新(化名)和其女友劉穎讼渊,沒想到半個月后,有當地人在樹林里發(fā)現了一具尸體尊剔,經...
    沈念sama閱讀 45,782評論 1 316
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,957評論 3 337
  • 正文 我和宋清朗相戀三年菱皆,在試婚紗的時候發(fā)現自己被綠了须误。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,102評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡仇轻,死狀恐怖京痢,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情篷店,我是刑警寧澤祭椰,帶...
    沈念sama閱讀 35,790評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站疲陕,受9級特大地震影響方淤,放射性物質發(fā)生泄漏。R本人自食惡果不足惜蹄殃,卻給世界環(huán)境...
    茶點故事閱讀 41,442評論 3 331
  • 文/蒙蒙 一携茂、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧诅岩,春花似錦讳苦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,996評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至式廷,卻和暖如春咐扭,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,113評論 1 272
  • 我被黑心中介騙來泰國打工草描, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留览绿,地道東北人。 一個月前我還...
    沈念sama閱讀 48,332評論 3 373
  • 正文 我出身青樓穗慕,卻偏偏與公主長得像饿敲,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子逛绵,可洞房花燭夜當晚...
    茶點故事閱讀 45,044評論 2 355

推薦閱讀更多精彩內容