Boost.Python:来自 python 中C++对象:无法添加到基类列表中C++

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

本文关键字:C++ 添加 基类 列表 python Python Boost 对象 来自      更新时间:2023-10-16

我正在尝试通过继承来扩展Python中现有的C++对象。我可以成功地做到这一点,并运行在 Python 中覆盖的虚拟方法。但是,当我尝试将 python 对象添加到 C++ Base 对象类型(python 类已覆盖的基本对象(的指针列表中时,我收到一个类型错误:"尝试附加无效类型">

我确信此错误是由于从派生*到base*开始没有"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*>>());
}

蟒 从沙盒导入 *

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.

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

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

但是,尝试这样做将暴露有关BaseWrap公开方式的其他问题:

  • sandbox.Base类必须是可从 Python 构造的,因此绑定不能提供boost::python::no_init作为其初始值设定项规范。 通常,仅当C++对象从C++显式实例化并传递给 Python 时,例如通过工厂函数,才需要使用boost::python::no_init
  • T BaseWrap时,Base*HeldType不能满足HeldType的要求。 特别是,HeldType要么需要:BaseWrap,一个派生自BaseWrap的类,要么是boost::python::pointee<Base*>::type BaseWrap的可取消引用类型,要么是派生自BaseWrap的类。 有关要求详细信息,请参阅 class_ 规范。

这些问题可以通过公开类来解决,如下所示:

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

下面是一个完整的示例,演示如何将派生自C++公开类的对象传递给通过vector_indexing_suite公开的C++向量:

#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*到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. 
};