Boost.Python:从 python 中的 C++ 对象继承:无法添加到 C++ 基础 class 列表
Boost.Python: inheret from C++ object in python: Cannot add to C++ base class list
我正在尝试通过继承扩展 Python 中现有的 C++ 对象。
我可以成功地做到这一点并且 运行 虚拟方法在 Python 中被覆盖。但是,当我尝试将 python 对象添加到 C++ Base 对象类型的指针列表(python class 已覆盖的 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()
函数接收一个类型正确的参数;但是,该参数有一个不合适的 value。在 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
class 必须可以从 Python 构造,因此绑定不能提供 boost::python::no_init
作为其初始化规范。通常,当 C++ 对象从 C++ 显式实例化并传递给 Python 时,人们只想使用 boost::python::no_init
,例如通过工厂函数。
- 当
T
为BaseWrap
时,Base*
的HeldType
不满足HeldType
的要求。特别是,HeldType
需要是:BaseWrap
、派生自 BaseWrap
的 class 或 boost::python::pointee<Base*>::type
为 [=20= 的可解引用类型] 或从 BaseWrap
派生的 class。有关要求的详细信息,请参阅 class_
规范。
这些可以通过公开 class 来解决,如下所示:
namespace python = boost::python;
python::class_<BaseWrap, boost::noncopyable>("Base", python::init<>())
.def("f", python::pure_virtual(&Base::f))
;
这是一个完整的示例 demonstrating 将从公开的 C++ 派生的对象 class 传递给通过 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])
return 是如何不同的值,因为一个是通过 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()
returned 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 中现有的 C++ 对象。 我可以成功地做到这一点并且 运行 虚拟方法在 Python 中被覆盖。但是,当我尝试将 python 对象添加到 C++ Base 对象类型的指针列表(python class 已覆盖的 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()
函数接收一个类型正确的参数;但是,该参数有一个不合适的 value。在 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
class 必须可以从 Python 构造,因此绑定不能提供boost::python::no_init
作为其初始化规范。通常,当 C++ 对象从 C++ 显式实例化并传递给 Python 时,人们只想使用boost::python::no_init
,例如通过工厂函数。 - 当
T
为BaseWrap
时,Base*
的HeldType
不满足HeldType
的要求。特别是,HeldType
需要是:BaseWrap
、派生自BaseWrap
的 class 或boost::python::pointee<Base*>::type
为 [=20= 的可解引用类型] 或从BaseWrap
派生的 class。有关要求的详细信息,请参阅class_
规范。
这些可以通过公开 class 来解决,如下所示:
namespace python = boost::python;
python::class_<BaseWrap, boost::noncopyable>("Base", python::init<>())
.def("f", python::pure_virtual(&Base::f))
;
这是一个完整的示例 demonstrating 将从公开的 C++ 派生的对象 class 传递给通过 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])
return 是如何不同的值,因为一个是通过 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()
returned 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.
};