zoukankan      html  css  js  c++  java
  • python嵌入C++ boost.python如何在C++中调用含有不定长参数tuple变量和关键字参数dict变量的函数

        这个问题是在我尝试利用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的特例。

  • 相关阅读:
    Step by step Dynamics CRM 2013安装
    SQL Server 2012 Managed Service Account
    Step by step SQL Server 2012的安装
    Step by step 活动目录中添加一个子域
    Step by step 如何创建一个新森林
    向活动目录中添加一个子域
    活动目录的信任关系
    RAID 概述
    DNS 正向查找与反向查找
    Microsoft Dynamics CRM 2013 and 2011 Update Rollups and Service Packs
  • 原文地址:https://www.cnblogs.com/rocketfan/p/1604241.html
Copyright © 2011-2022 走看看