【问题标题】:Boost.Python: inheret from C++ object in python: Cannot add to C++ base class listBoost.Python:从 python 中的 C++ 对象继承:无法添加到 C++ 基类列表
【发布时间】:2015-10-27 06:49:36
【问题描述】:

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

我确信这个错误是由于没有从派生* 到基础* 的“隐式转换”功能。在 C++ 中,这将被定义为:implicitly_convertible,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

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

【问题讨论】:

    标签: python c++ boost boost-python


    【解决方案1】:

    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
    • TBaseWrap时,Base*中的一个HeldType不满足HeldType的要求。特别是,HeldType 要么需要是:BaseWrap,派生自 BaseWrap 的类,要么是 boost::python::pointee&lt;Base*&gt;::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++ 向量:

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

    【讨论】:

    • 感谢您的详细解释。我希望我能更多地支持答案。我正在努力解决“无法迭代 base_list,因为迭代器的值将尝试执行 base* 到 Python 的转换,这种转换不存在。”现在部分。但其余的工作完美。
    • @user3035260 当迭代BaseList 对象时,Python 不能安全地假定所有base* 都是base_wrap 的实例。考虑一个 C++ 类型 foo 派生自 base 并且指向 foo 实例的指针被推入保存在 Python 对象中的 std::vector&lt;base*&gt; 的情况。有关vector_indexing_suite 的更多详细信息,请考虑阅读此answer
    猜你喜欢
    • 2020-08-29
    • 1970-01-01
    • 2014-02-24
    • 2021-08-25
    • 1970-01-01
    • 2017-05-23
    • 1970-01-01
    • 1970-01-01
    • 2012-12-08
    相关资源
    最近更新 更多