• 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的特例。

  • 相关阅读:
    C#中的委托是什么?事件是不是一种委托?
    SQL重点复习
    数据库生成脚本
    用Winfrom动态生成SQL的insert语句
    如何实现远程连接SQL Server 2008 Express
    跨页面传送
    win7 防火墙开启ping
    关于*.class和*.jar的几个基本认识
    使用cobertura确定测试代码的覆盖率
    Java学习笔记之I/O
  • 原文地址:https://www.cnblogs.com/rocketfan/p/1604241.html
Copyright © 2020-2023  润新知