Python調用C++的幾種方法

參考:https://www.jb51.net/article/104159.htm
參考:https://blog.csdn.net/zong596568821xp/article/details/81133511


前言
caffe2用python來構造tensor, blob,op,workspace等,生成protobuf,傳給後面的C++執行。那運行的整個過程,個人就有點好奇了。本文爲僅爲python調用C++的幾種方法,不涉及任何caffe2的運行機制。

大家都知道Python的優點是開發效率高,使用方便,C++則是運行效率高,這兩者可以相輔相成,不管是在Python項目中嵌入C++代碼,或是在C++項目中用Python實現外圍功能,都可能遇到Python調用C++模塊的需求,下面列舉出集中c++代碼導出成Python接口的幾種基本方法。


1、原生態導入

Python解釋器就是用C實現,因此只要我們的C++的數據結構能讓Python認識,理論上就是可以被直接調用的。我們實現test1.cpp如下

#include <Python.h>

int Add(int x, int y){
	return x+y;
}

PyObject* WrappAdd(PyObject* self, PyObject* args){
	int x, y;
	if(!PyArg_ParseTuple(args, "ii", &x, &y)){
		return NULL;
	}
	return Py_BuildValue("i", Add(x, y));
}

PyObject* WrappDel(PyObject* self, PyObject* args){
	int x, y;
	if(!PyArg_ParseTuple(args, "ii", &x, &y)){
		return NULL;
	}
	return Py_BuildValue("i", Del(x, y));
}

static PyMethodDef test_methods[]={
	{"Add", WrappAdd, METH_VARARGS,"something"},
	{"Del", WrappDel, METH_VARARGS,"something"},
	{NULL, NULL}
};

extern "C"
void inittest1(){
	Py_InitModule("test1", test_methods);
}

編譯命令如下

g++ -fPIC -shared test1.cpp -o test1.so -I/usr/include/python2.7

其中:

-fPIC: 生成位置無關目標代碼,適用於動態連接;
-L path:表示在path目錄中搜索庫文件;
-I path:表示在path目錄中搜索頭文件;
-o file:指定輸出文件爲file
-shared: 生成一個共享庫文件

運行Python解釋器,測試如下:

>>> import test1
>>> test1.Add(1, 2)
3

Note That:

  • 如果生成的動態庫名字爲test1,則源文件裏必須有inittest1這個函數,且Py_InitModule的第一個參數必須是“test1”,否則Python導入模塊會失敗
  • 如果是cpp源文件,inittest1函數必須用extern "C"修飾,如果是c源文件,則不需要。原因是Python解釋器在導入庫時會尋找initxxx這樣的函數,而C和C++對函數符號的編碼方式不同,C++在對函數符號進行編碼時會考慮函數長度和參數類型,具體可以通過nm test1.so查看函數符號,c++filt工具可通過符號反解出函數原型

  • 2、通過boost實現
    使用和上面同樣的例子,實現test2.cpp如下:

    #include <boost/python/module.hpp>
    #include <boost/python/def.hpp>
    using namespace boost::python;
    
    int Add(const int x, const int y){
    	return x+y;
    }
    
    int Del(const int x, const int y){
    	return x-y;
    }
    
    BOOST_PYTHON_MODULE(test2){
    	def(“Add", Add);
    	def("Del", Del);
    }
    

    其中,BOOST_PYTHON_MODULE的參數爲要導出的模塊名字,編譯命令如下:

    g++ test2.cpp -fPIC -shared -o test2.cpp -I/usr/include/python2.7 -I/usr/local/include -L/usr/local/lib -lboost_python
    

    Note That:

    注意: 編譯時需要指定boost頭文件和庫的路徑,我這裏分別是/usr/local/include和/usr/local/lib 或者通過setup.py導出模塊

    #!/usr/bin/env python
    from distutils.core import setup
    from distutils.extension import Extension
    
    setup(name="PackageName",
    	ext_modules=[
    		Extension("test2", ["test2.cpp"], 
    		libraries=["boost_python"])
    ])
    

    其中,Extension的第一個參數爲模塊名,第二個參數爲文件名
    執行如下命令

    python setup.py build
    

    這時會生成build目錄,找到裏面的test2.so,並進入同一級目錄,驗證如下

    >>>import test2
    >>>test2.Add(1,2)
    3
    >>>test2.Del(1,2)
    -1
    

    3、導出類
    test3.cpp實現如下

    #include <boost/python.hpp>
    using namespace boost::python;
    
    class Test{
    	public:
    	int Add(const int x, const int y){
    		return x+y;
    	}	
    
    	int Del(const int x, const int y){
    		return x-y;
    	}
    };
    
    BOOST_PYTHON_MODULE(test3){
    	class_<Test>("Test")
    	.def("Add", &Test::Add)
    	.def("Del", &Test::Del);
    }
    

    Note That:

    注意:BOOST_PYTHON_MODULE裏的.def使用方法有點類似Python的語法,等同於

    class_<Test>("Test").def("Add", &Test::Add);
    class_<Test>("Test").def("Del", &Test::Del);
    

    編譯命令如下:

    g++ test3.cpp -fPIC -shared -o test3.o -I/usr/local/include/boost -I/usr/include/python2.7 -L/usr/local/lib -lboost_python
    

    測試如下:

    >>>import test3
    >>>test = test3.Test()
    >>>test.Add(1,2)
    3
    >>>test.Del(1)
    -1
    

    4、導出變參函數*
    test4.cpp實現如下:

    #include <boost/python.hpp>
    using namespace boost::python;
    
    class Test{
    	public:
    	int Add(const int x, const int y, const int z=100){
    		return x+y+z;
    	}
    };
    
    int Del(const int x, const int y, const int z = 100){
    	return x-y-z;
    }
    BOOST_PYTHON_MEMBER_FUNCTION_OVERLOADS(Add_member_overloads, Add, 2, 3)
    BOOST_PYTHON_FUNCTION_OVERLOAD(Del_overloads, Del, 2, 3)
    
    BOOST_PYTHON_MODULE(test4){
    	class_<test>("Test")
    	.def("Add", &Test::Add, Add_member_overloads(args("x", "y", "z"), "something"));
    	def("Del", Del, Del_overloads(args("x", "y", "z"), "something"));
    }
    

    Note That: 這裏的Add和Del函數均採用默認參數,Del爲普通函數, Add爲類成員函數,這裏分別調用了不同的宏,宏的最後兩個參數分別代表函數的最少參數的個數和最多參數的個數。
    編譯命令如下:

    g++ test4.cpp -fPIC -shared -o test4.so -I/usr/local/python2.7 -I/usr/local/include/boost -L/usr/local/lib -lboost_python
    

    測試如下:

    >>>import test4
    >>>test = test4.Test()
    >>>print test.Add(1, 2)
    103
    >>>print test.Add(1, 2, z=3)
    6
    >>>print test4.Del(1, 2)
    -1
    >>>print test4.Del(1, 2, z=3)
    -1
    

    5、導出帶Python對象的接口

    既然是導出爲Python接口,調用者難免會使用Python特有的數據結構,比如tuple,list,dict,由於原生態方法太麻煩,這裏只記錄boost的使用方法,假設要實現如下的Python函數功能

    def Square(list_a){
    	return [x * x for x in list_a]
    }
    

    即對傳入的list每個元素計算平方,返回list類型的結果,代碼如下

    #include <boost/python.hpp>
    
    boost::python::list Square(boost::python::list& data){
    	boost::python::list ret;
    	for(int i=0; i<len(data);++i){
    		ret.append(data[i] * data[i]);
    	}
    	return ret;
    };
    
    BOOST_PYTHON_MODULE(test5){
    	def("Square", Square);
    }
    

    編譯命令如下:

    g++ test5.cpp -fPIC -shared -o test5.so -I/usr/include/python2.7 -I/usr/local/include/boost -L/usr/local/lib -lboost_python
    

    測試如下:

    >>>import test5
    >>>test5.Square([1, 2, 3])
    [1, 4, ,9]
    

    Note That:boost實現了boost::python::tuple, boost::python::list, boost::python::dict這幾個數據類型,使用方法基本和Python保持一致,具體方法可以查看boost頭文件裏的boost/python/tuple.hpp及其它對應的文件

    另外一個函數boost::python::make_tuple(),使用方法如下:

    boost::python::tuple(int a, int b, int c){
    	return boost::python::make_tuple(a, b, c);
    }
    
    發表評論
    所有評論
    還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
    相關文章