這個問題是在我嘗試利用pygraphviz嵌入我的C++代碼繪製二叉樹的時候發現的.找了半天資料,這裡我把幾種常用的C++調用
PYTHON利用 boost.python 的方法作一個總結,希望能讓別人少走彎路,因為有些內容還找不到中文文檔,雖然都不難但是開始摸索
還是費時間的.
我個人認為boost.python真的是非常的COOL,基本上不需要去學習那個看了就頭大用著也不方便的python c api了,唯一的缺點
是目前相關的資料太少,甚至官網上也解釋不夠詳細.
前面我寫了一篇python嵌入c++的入門文章包括安裝和環境配置,介紹了如何利用 boost.python方便的傳遞C++代碼中的參數,調用 python函數.boost.python入門教程 ----python 嵌入c++
這個boost.python官網上的tourial也有介紹.
- 首先第一種常用方式是 python::boost::exec
#include <boost/python.hpp>
using namespace boost::python;
//引入python解譯器
Py_Initialize();
//引入__main__ 範圍
object main_module = import("__main__");
object main_namespace = main_module.attr("__dict__");
exec("print('Hello world!')", main_namespace);
//exec + python command string + 範圍
- 第二種方法是利用boost::python::object對象
第一種方法雖然很好但是不方便和C++互動資料,如傳遞C++中的資料作為參數給python函數,以及C++接受python執行函數後
的傳回值. 那麼怎麼用object調用python函數呢,很簡單看下面的代碼就瞭解了.
我在simple.py中定義了函數
def foo(int i = 3):
return i + 2008
這樣一個簡單的函數, 通過將simple.py利用boost::python::exec_file將其引入到__main__範圍,我們在__main__.__dict__
也就是main_namespace中取到命名為foo的函數對象,並將其轉換成boost::python::object使用. 這裡我們給這個object
同樣命名為foo,調用foo(5)即調用python 函數foo(5)返回2013,注意在C++環境中要用extract<int>將這個值取到.
object simple = exec_file("simple.py",main_namespace, main_namespace);
object foo = main_namespace["foo"];
int val = extract<int>(
foo(
5
));
cout << "Python has caculated foo as " << val << endl;
- 和C++不同,python函數支援不定長參數和關鍵字參數,遇到這種情況如何調用呢?
如果是簡單的foo(a,b,c)這樣的python函數按照上面的形式調用即可,但是象foo(*args), foo(**kargs),foo(*args,**kargs)
foo(n,**kargs)諸如此類的函數怎麼在C++中利用boost::python::object調用呢?
恩,這是本文的重點,先看下什麼是不定長參數和關鍵字參數.我直接copy一下賴勇浩部落格上的解釋吧,非常的清晰.
http://blog.csdn.net/lanphaday/archive/2009/05/08/4159346.aspx
不定參數
在 C/C++ 中,不定參數可以算得上一節提高篇的課程。因為它的 va_list、va_start和 va_end 等是侵入式的,理解起來並不容易;此外由於 C/C++ 都是靜態強型別語言,在運行時資料並不攜帶類型資訊,因此不定參數函數更像是一個調用協議,需要函數定義者和使用者之間通過文檔、注釋等方式進行溝通;或 者像 printf() 函數那樣用 fmt 參數隱式指出參數類型,然後進行顯式轉型。
不定參數在 Python 中則簡單得多。再回過頭來年一下 C/C++,其實 va_list,完全是一個 tuple 實現,因為它可以持有不同資料類型的指標(通過void* 來實現)。得益於 Python 函數調用時的 boxing 和 unboxing 操作,Python 可以為不定參數的存取提供更為簡潔的實現。如:
def foo(*args):
for arg in args: print arg
在 Python 中可以使用 *args 文法為函數定義不定參數,其中 args 相當於 C/C++ 的 va_list,它是一個 tuple 類型的參數容器,所以無需所謂的 va_start、va_end 就可以簡單遍曆所有參數了。
在 Python 中,不定參數可以直接用 tuple 參數調用,如:
names = ('laiyonghao', 'denggao', 'liming')
foo(*names) # 留意符號 *
關鍵字參數
儘管不定參數給函數帶來了很多便利性,但 Python 的關鍵字參數尤為神通廣大。關鍵字參數是指以下形式定義的參數:
def foo(**kw): pass
其中 kw 本質上是一個 dict 對象,所以可以這樣調用 foo:
foo( **{'a' : 1, 'b' : 2, 'c' : 3} )
看起來有點眼熟?對的,在“第一貼”(http://blog.csdn.net/lanphaday/archive/2008/08/31/2857813.aspx)裡 DIP 的例 2.1 就有這幾行代碼:
if __name__ == "__main__":
myParams = {"server":"mpilgrim",
"database":"master",
"uid":"sa",
"pwd":"secret"
}
print buildConnectionString(myParams)
這個 buildConnectionString(myParams) 和前文的 foo() 調用很像吧,而且利用關鍵字參數後更複雜了。其實不是這樣的,如果使用關鍵字參數,例2.1 可以寫得優為簡潔:
def buildConnectionString(**params):
"""Build a connection string from a dictionary of parameters.
Returns string."""
return ";".join("%s=%s" % (k, v) for k, v in params.iteritems())
if __name__ == "__main__":
print buildConnectionString(
server = ‘mpilgrim’,
database = ‘master’
uid = ‘sa’
pwd = ‘secret’)
除了更加優雅外,也比以前一種寫法提升效能呢。
本文來自CSDN部落格,轉載請標明出處:http://blog.csdn.net/lanphaday/archive/2009/05/08/4159346.aspx
OK,下面重點介紹boost.python中怎麼在C++中調用這樣的帶有不定參數,關鍵字參數的函數呢?
torial 裡面並沒有介紹,那麼我們去看boost::python::object的manu吧.
或者 直接看原始碼<python/object_core.hpp>
template <class U>
class object_operators : public def_visitor<U>
{
public:
// function call
//
object operator()() const;
detail::args_proxy operator* () const;
object operator()(detail::args_proxy const &args) const;
object operator()(detail::args_proxy const &args,
detail::kwds_proxy const &kwds) const;
這個正是我們所需要的,()操作符重載,從而支援不定參數,上面紅色的第一個函數,和不定參數+關鍵字參數,紅色的第二個函數.
Class template
object_operators
observer functions
object operator()() const;
template <class A0>
object operator()(A0 const&) const;
template <class A0, class A1>
object operator()(A0 const&, A1 const&) const;
...
template <class A0, class A1,...class An>
object operator()(A0 const& a1, A1 const& a2,...An const& aN) const;
-
Effects: call<object>(object(*static_cast<U*>(this)).ptr(), a1, a2,...aN)
-
這個對應普通的python 函數調用
object operator()(detail::args_proxy const &args) const;
-
Effects: call object with arguments given by the tuple args
-
對應不定參數的python函數調用
object operator()(detail::args_proxy const &args,
detail::kwds_proxy const &kwds) const;
-
Effects: call object with arguments given by the tuple args, and named arguments given by the dictionary kwds
-
對應不定參數+關鍵字參數 dict的python函數的調用
OK,現在所有的python函數我們都可以在c++中利用boost::python::object調用了!很COOL吧!
看一個具體的例子:
在我試圖在我的C++程式中使用pygraphviz的時候,我需要調用下面這樣一個python函數.
add_node(self, n, **attr) method of pygraphviz.agraph.AGraph instance
Add a single node n.
If n is not a string, conversion to a string will be attempted.
String conversion will work if n has valid string representation
(try str(n) if you are unsure).
>>> G=AGraph()
>>> G.add_node('a')
>>> G.nodes()
['a']
>>> G.add_node(1) # will be converted to a string
>>> G.nodes()
['a', '1']
Attributes can be added to nodes on creation
>>> G.add_node(2,color='red')
該參數列表由一個普通參數和一個關鍵字參數構成,你可能會想上面的幾種operator()的重載函數中沒有這種形式啊?
沒有關係,解決的辦法是將普通參數在這裡當作tuple看待,而且你只能這麼做:)否則運行通不過的!
具體的辦法是如果你的函數有n 個普通參數在關鍵字參數前面那麼你就產生並傳遞一個有n個元素組成的tuple,
如果是形如foo(**kargs)這樣的函數,那麼你也需要先傳遞一個空tuple,
void sort(args_proxy const &args, kwds_proxy const &kwds);
x.sort(*tuple(), **dict(make_tuple(make_tuple("reverse", true))));
// 等價於 Python 調用 x.sort(reverse=true)
好了下面看一下我調用add_node的代碼,注意add_node(1, color='red')表示產生一個node,它的關鍵字是1,而顏色是紅色,
我也可能會調用 add _node(2, lable='abc')表示該節點關鍵字是2,而它將會被輸出顯示的標誌是abc.
void print(std::string result = "huff_tree.dot") {
using namespace boost::python; //for tree printing
Py_Initialize();
object main_module = import("__main__");
object main_namespace = main_module.attr("__dict__");
exec("import pygraphviz as pgv", main_namespace);
exec("tree_graph = pgv.AGraph(directed=True,strict=True)", main_namespace);
object tree_graph = main_namespace["tree_graph"];
tree_graph.attr("add_node")(*make_tuple(1), **dict(make_tuple(make_tuple("label", "Jordan"))));
exec("tree_graph.graph_attr['epsilon']='0.001'", main_namespace);
exec("tree_graph.layout('dot')", main_namespace);
exec("tree_graph.write('huff_tree.dot')", main_namespace);
}
恩,看下產生的huff_tree.dot檔案,node 的 key 是1, label是Jordan,完全正確:)
strict digraph {
graph [bb="0,0,70,36",
epsilon="0.001"
];
node [label="\N"];
1 [height="0.50",
label=Jordan,
pos="35,18",
width="0.97"];
}
圖片顯示如下:
關於上面代碼dict(make_tuple(make_tuple()))的解釋:
其實是這樣的對這樣一個tuple(tuple,tuple) 如 ((1,2),(3,4)) 執行dict操作得到 {1:2,3:4}
即dict(make_tuple(make_tuple(1,2),make_tuple(3,4))) = {1:2, 3:4}
而上面的例子women其實就是dict(make_tuple(make_tuple(1,2))這樣裡面只有一個tuple的特例。