python嵌入C++—— boost.python如何在C++中調用含有不定長參數tuple變數和關鍵字參數dict變數的函數

來源:互聯網
上載者:User

    這個問題是在我嘗試利用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的特例。

 

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在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.