python - boost .Python : inheret from C++ object in python: Cannot add to C++ base class list

标签 python c++ boost boost-python

我正在尝试通过继承在 Python 中扩展现有的 C++ 对象。 我可以成功地做到这一点,并运行在 Python 中重写的虚拟方法。然而,当我尝试将 python 对象添加到 C++ Base 对象类型的指针列表(python 类已覆盖的 Base 对象)时,我收到类型错误:'Attempting to append an invalid type'

我确信这个错误是由于没有开始从派生*到基础*的“implicitly_convertible”功能。在 C++ 中,这将定义为:implicitly_convertible<[Derived_from_base],Base>();。 是否可以在 python 中定义它?

我怎样才能做到这一点?

这是重现此行为的示例代码。

C++

struct Base {
    virtual ~Base() {}
    virtual int f() = 0;
};
struct A {
    std::vector<Base*>& GetBaseList() { return m_base_List; }
    std::vector<Base*> m_base_List;
};
struct BaseWrap : Base, wrapper<Base> {
    int f() { return this->get_override("f")(); }
};

BOOST_PYTHON_MODULE(sandbox)
{
    class_<BaseWrap, Base*, boost::noncopyable>("Base", no_init)
        .def("f", pure_virtual(&Base::f));

    class_<A, A*>("A", init<>())
        .add_property("baseList", make_function(&A::GetBaseList, return_internal_reference<>()));

    //implicitly_convertible<[Derived_from_base]*,Base*>();
    class_<std::vector<Base*>>("BaseList").def(vector_indexing_suite<std::vector<Base*>>());
}

python 从沙箱导入 *

class derived(Base):
    def __init__(self):
        self.name = "test"
    def f(self):
        print("Hello Derived!")

d = derived()
d.f()          # Output: Hello Derived!

a = A()
a.baseList.append(d) # TypeError: Attempting to append an invalid type

任何帮助或想法将不胜感激。

最佳答案

BaseList.append()函数接收具有正确类型的参数;但是,该参数有一个不合适的。在 Python 中,derived初始化程序未初始化 sandbox.Base它的层次结构的一部分。这导致 Boost.Python 对象不包含 C++ BaseWrap目的。因此,当 BaseList.append()尝试提取 C++ BaseWrap对象,它失败并抛出错误。

class derived(Base):
    def __init__(self):
        self.name = "test"
        # Base is not initialized.
    def f(self):
        print("Hello Derived!")

d = derived()
d.f() # `derived.f()` is resolved through Python's method-resolution-order.  
      # It is not invoking `BaseWrap::f()`.

a = A()
a.baseList.append(d) # d does not contain a BaseWrap object, so this throws.

要解决此问题,请显式调用 Base.__init__()derived.__init__() 内:

class derived(Base):
    def __init__(self):
        self.name = "test"
        Base.__init__(self)

但是,尝试这样做会出现其他问题,即如何 BaseWrap暴露了:

  • sandbox.Base类必须可以从 Python 构造,因此绑定(bind)不能提供 boost::python::no_init作为它的初始化规范。通常,人们只想使用 boost::python::no_init当 C++ 对象从 C++ 显式实例化并传递给 Python 时,例如通过工厂函数。
  • 何时TBaseWrap , 一个 HeldTypeBase*未能满足HeldType的要求.特别是 HeldType要么需要是:BaseWrap ,一个派生自 BaseWrap 的类,或一个可取消引用的类型 boost::python::pointee<Base*>::typeBaseWrap或派生自 BaseWrap 的类.查看 class_ 需求详情规范。

这些可以通过如下公开类来解决:

namespace python = boost::python;
python::class_<BaseWrap, boost::noncopyable>("Base", python::init<>())
  .def("f", python::pure_virtual(&Base::f))
  ;

这是一个完整的例子demonstrating将派生自 C++ 公开类的对象传递给通过 vector_indexing_suite 公开的 C++ vector :

#include <vector>
#include <boost/python.hpp>
#include <boost/python/suite/indexing/vector_indexing_suite.hpp>

struct base
{
  virtual ~base() {}
  virtual int perform() = 0;
};

struct base_wrap: base, boost::python::wrapper<base>
{
  int perform() { return int(this->get_override("perform")()) - 10; }
};

BOOST_PYTHON_MODULE(example)
{
  namespace python = boost::python;
  python::class_<base_wrap, boost::noncopyable>("Base", python::init<>())
    .def("perform", python::pure_virtual(&base::perform))
    ;

  python::class_<std::vector<base*>>("BaseList")
    .def(python::vector_indexing_suite<std::vector<base*>>())
    ;

  python::def("do_perform", +[](base* object) {
    return object->perform();
  });
}

交互使用:

>>> import example
>>> class derived(example.Base):
...     def __init__(self):
...         self.name = "test"
...         example.Base.__init__(self)
...     def perform(self):
...         return 42
...       
>>> d = derived()
>>> base_list = example.BaseList()
>>> base_list.append(d)
>>> assert(len(base_list) == 1)
>>> assert(base_list[0].perform() == 42)
>>> assert(example.do_perform(base_list[0]) == 32)

对于集合和指针,通常有一些注意事项。在这种情况下:

  • BaseList对象不具有其元素所引用的对象的共享所有权。请注意确保容器引用的对象的生命周期至少与容器本身一样长。在上面的例子中,如果对象 d被删除,然后调用 base_list[0].perform()可能导致未定义的行为。
  • 不能遍历 base_list ,因为迭代器的值将尝试执行 base* -to-Python 转换,不存在。

上面的例子也展示了函数调度的不同。如果 Python 可以直接调用一个方法,它将使用自己的方法解析机制来实现。注意如何 base_list[0].perform()example.do_perform(base_list[0])返回不同的值,因为一个通过 base_wrap::perform() 发送哪个操纵结果,另一个不操纵结果。

原代码中:

class derived(sandbox.Base):
    ...
    def f(self):
        print("Hello Derived!")

d = derived()
d.f()

Python 知道 derived.f() , 调用 d.f()不会通过 BaseWrap::f() 发送.如果BaseWrap::f()已经被调用,它会被抛出因为derived.f()返回 None , 将无法转换为 int :

struct BaseWrap : Base, wrapper<Base> {
    int f() { return this->get_override("f")(); }
                        // ^~~ returns a boost::python::object, faling to 
                        //     extract `int` will throw. 
};

关于python - boost .Python : inheret from C++ object in python: Cannot add to C++ base class list,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/33361537/

相关文章:

c++ - 拦截对 std::streambuf 的所有写入

c++ - 2个小错误,我无法用C++理解!

c++ - 当存在用户定义的移动分配运算符时,模板化的移动分配运算符被删除

python - python 搜索中出现'builtin_function_or_method'对象不可迭代'错误?

python - 如何以简单的方式删除 pandas 数据框中的特殊行

python - Pymodbus/Twisted 异步客户端重新连接

python - Django/一般缓存问题

c++ - 包含 BGL : call to strong_components fails to compile when random_spanning_tree. hpp

c++ - 加载共享库时出错 : libboost_program_options. so.1.65.1

c++ - 对于具有单个接受器的线程化 boost::asio 服务器,我们是否需要每个线程多个 io_service