在 boost python 中暴露 C++ 接口

Posted

技术标签:

【中文标题】在 boost python 中暴露 C++ 接口【英文标题】:Exposing C++ interface in boost python 【发布时间】:2013-11-18 18:15:04 【问题描述】:

示例代码说明:

struct Base

  virtual int foo() = 0;
;

struct Derived : public Base

  virtual int foo()
  
    return 42;
  
;

Base* get_base()

  return new Derived;


BOOST_PYTHON_MODULE(libTestMod)

  py::class_<Base>("Base", py::no_init)
    .def("foo", py::pure_virtual(&Base::foo));

  py::def("get_base", get_base, py::return_internal_reference<>()); //ignore mem leak

Base::foo 在 python 中不会被覆盖 Base:foo 将在 c++ 中实现,但不应暴露给 python

尝试了上面的代码,但编译失败。

更新: 编译错误:

/path/to/boostlib/boost/1.53.0-0/common/include/boost/python/object/value_holder.hpp:66:11: error: cannot declare field 'boost_1_53_0::python::objects::value_holder<Base>::m_held' to be of abstract type 'Base'
Main.C:59:8: note:   because the following virtual functions are pure within 'Base':
Main.C:61:15: note:         virtual int Base::foo()

【问题讨论】:

您介意显示编译错误,或者您的问题是什么? 包含编译错误。 显然py::class_&lt;Base&gt;("Base", py::no_init) 想要创建Base 的实例,这是不可能的,因为Base 是抽象的。不过我对 boost python 没有经验,无法详细说明问题所在。 【参考方案1】:

抽象 C++ 类不能以这种方式暴露给 Boost.Python。 Boost.Python tutorial 给出了如何公开纯虚函数的示例。简而言之,当使用boost::python::pure_virtual装饰方法时,需要创建一个包装器类型以允许C++对虚函数进行多态解析,而虚函数实现将在Python对象的层次结构中委托多态解析函数。

struct BaseWrap : Base, boost::python::wrapper<Base>

  int foo()
  
    return this->get_override("foo")();
  
;

...

boost::python::class_<BaseWrap>("Base", ...)
  .def("foo", boost::python::pure_virtual(&Base::foo))
  ;

详细来说,当一个类型通过boost::python::class_公开时,HeldType默认为被公开的类型,HeldType是在Python对象中构造的。 class_ 文档指出:

模板参数:

T: 被包装的类 HeldType:指定实际嵌入在包装 T 实例 [...] 的 Python 对象中的类型。默认为T

因此,boost::python::class_&lt;Base&gt; 将失败,因为T = BaseHeldType = Base,Boost.Python 将尝试将HeldType 的对象实例化为代表Base 实例的Python 对象。此实例化将失败,因为Base 是一个抽象类。


这是一个完整的例子,展示了BaseWrap 类的使用。

#include <boost/python.hpp>

struct Base

  virtual int foo() = 0;
  virtual ~Base() 
;

struct Derived : public Base

  virtual int foo()
  
    return 42;
  
;

Base* get_base()

  return new Derived;


namespace python = boost::python;

/// @brief Wrapper that will provide a non-abstract type for Base.
struct BaseWrap : Base, python::wrapper<Base>

  BaseWrap() 

  BaseWrap(const Base& rhs)
    : Base(rhs)
  

  int foo()
  
    return this->get_override("foo")();
  
;

BOOST_PYTHON_MODULE(example)

  python::class_<BaseWrap>("Base")
    .def("foo", python::pure_virtual(&Base::foo));
    ;

  python::def("get_base", &get_base,
              python::return_value_policy<python::manage_new_object>());

及其用法:

>>> import example
>>> class Spam(example.Base):
...     pass
... 
>>> Spam().foo()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
RuntimeError: Pure virtual function called
>>> class Egg(example.Base):
...     def foo(self):
...         return 100
... 
>>> e = Egg()
>>> e.foo()
100
>>> d = example.get_base()
>>> d.foo()
42

可以在 Boost.Python 中公开一个抽象类,方法是在没有默认初始化程序 (boost::python::no_init) 和不可复制 (boost::noncopyable) 的情况下公开它。缺少初始化程序会阻止 Python 类型从它派生,从而有效地防止覆盖。此外,Base::foo()Derived 在 C++ 中实现的实现细节无关紧要。如果 Python 根本不应该知道 foo() 方法,则省略通过 def() 公开它。

#include <boost/python.hpp>

struct Base

  virtual int foo() = 0;
  virtual ~Base() 
;

struct Derived
  : public Base

  virtual int foo()
  
    return 42;
  
;

struct OtherDerived
  : public Base

  virtual int foo()
  
    return 24;
  
;

Base* get_base()

  return new Derived;


Base* get_other_base()

  return new OtherDerived;


BOOST_PYTHON_MODULE(example)

  namespace python = boost::python;
  python::class_<Base, boost::noncopyable>("Base", python::no_init)
    ;

  python::class_<Derived, python::bases<Base> >("Derived", python::no_init)
    .def("foo", &Base::foo)
    ;

  python::class_<OtherDerived, python::bases<Base> >(
      "OtherDerived", python::no_init)
    ;

  python::def("get_base", &get_base,
              python::return_value_policy<python::manage_new_object>());

  python::def("get_other_base", &get_other_base,
              python::return_value_policy<python::manage_new_object>());

互动使用:

>>> import example
>>> b = example.get_base()
>>> b.foo()
42
>>> b = example.get_other_base()
>>> b.foo()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'OtherDerived' object has no attribute 'foo'

【讨论】:

上述解决方案只有在Base的所有纯虚函数都暴露在python中时才有效。有没有办法只公开Base 的一部分功能? @balki:上面暴露的类型没有纯虚函数,暴露类型既不需要也不暴露成员函数。如果foo() 不应该暴露,那么不要通过class_ 上的def() 暴露它。如果从get_base() 返回的类型需要比BaseWrap 提供的接口更窄的接口,则将Derived 公开为仅具有所需方法和默认Bases 模板参数的类型。 我看到我们可以通过不指定def来避免在python中暴露。但是它需要BaseWrap 来实现所有的纯虚函数。即用 dummy impl 重写 Base 的接口。我想没有更好的办法了。【参考方案2】:

抽象类实际上可以在没有包装器的情况下暴露给 Boost.Python。 诀窍是使用 boost::noncopyable 定义您的类并避免 pure_virtual 方法包装器。

这是更正后的代码(使用 Boost.Python 1.47.0 和 Python 2.7.6 测试):

#include <boost/python/class.hpp>
#include <boost/python/def.hpp>
#include <boost/python/module.hpp>

struct Base

  virtual int foo() = 0;
;

struct Derived : public Base

  virtual int foo()
  
    return 42;
  
;

Base* get_base()

  return new Derived;


BOOST_PYTHON_MODULE(libTestMod)

    namespace py = boost::python;

    py::class_<Base, boost::noncopyable>("Base", py::no_init)
        .def("foo", &Base::foo);

    py::def("get_base", get_base,
        py::return_value_policy<py::reference_existing_object>()); //ignore mem leak

测试:

$ python
Python 2.7.6 (default, Mar 31 2014, 16:04:58) 
[GCC 4.7.3] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import libTestMod
>>> base = libTestMod.get_base()
>>> print base.foo()
42

【讨论】:

如何导出派生类?如果我这样做:py::class_&lt;Derived, boost::noncopyable&gt;("Derived", py::init&lt;&gt;()).def("foo", &amp;Derived::foo); 我得到Traceback (most recent call last): File "code.py", line 3, in &lt;module&gt; print base.foo() Boost.Python.ArgumentError: Python argument types in Derived.foo(Derived) did not match C++ signature: foo(Derived lvalue) 导出派生类为:py::class_&lt;Derived, py::bases&lt;Base&gt; &gt;("Derived"); foo() 方法将被自动继承。

以上是关于在 boost python 中暴露 C++ 接口的主要内容,如果未能解决你的问题,请参考以下文章

将 C++ 暴露给来自 BoostPython 的 Python 错误

使用 Boost 暴露静态常量

Boost.Python 列出所有暴露的类和属性

使用 Boost.Python 将 Python 转换为 C++ 函数

Boost.Python 如何拥有 C++ 类?

用 boost.python 暴露 std::vector<double>