我有混合系统(c ++,boost python)。 在我的c ++代码中有非常简单的层次结构
class Base{...}
class A : public Base{...}
class B : public Base{...}
另外2个业务(使用c ++)方法
smart_ptr<Base> factory() //this produce instances of A and B
void consumer(smart_ptr<A>& a) //this consumes instance of A
在python代码中,我使用factory
创建A的实例并尝试调用使用者方法:
v = factory() #I'm pretty sure that it is A instance
consumer(v)
绝对合理我有例外:
消费者(Base)中的Python参数类型与C ++签名不匹配:consumer(class A {lvalue})
之所以发生这种情况,是因为无法告诉Boost应该进行一些转换工作。
有没有办法指定动态投射行为? 提前谢谢。
答案 0 :(得分:4)
对于boost::shared_ptr
,Boost.Python通常提供所需的功能。在这种特殊情况下,只要模块声明定义to_python
由Base
持有,并且Boost.Python被告知{{}},就不需要显式提供自定义boost::shared_ptr<Base>
转换器。 1}}继承自A
。
Base
Boost.Python目前不支持自定义左值转换器,因为它需要更改核心库。因此,BOOST_PYTHON_MODULE(example) {
using namespace boost::python;
class_<Base, boost::shared_ptr<Base>,
boost::noncopyable>("Base", no_init);
class_<A, bases<Base>,
boost::noncopyable>("A", no_init);
def("factory", &factory);
def("consumer", &consumer);
}
函数要么需要接受consumer
值,要么接受const-reference。以下任何一个签名都应该有效:
boost:shared_ptr<A>
这是一个完整的例子:
void consumer(boost::shared_ptr<A> a)
void consumer(const boost::shared_ptr<A>& a)
用法:
#include <boost/python.hpp>
#include <boost/make_shared.hpp>
class Base
{
public:
virtual ~Base() {}
};
class A
: public Base
{
public:
A(int value) : value_(value) {}
int value() { return value_; };
private:
int value_;
};
boost::shared_ptr<Base> factory()
{
return boost::make_shared<A>(42);
}
void consumer(const boost::shared_ptr<A>& a)
{
std::cout << "The value of object is " << a->value() << std::endl;
}
BOOST_PYTHON_MODULE(example) {
using namespace boost::python;
class_<Base, boost::shared_ptr<Base>,
boost::noncopyable>("Base", no_init);
class_<A, bases<Base>,
boost::noncopyable>("A", no_init);
def("factory", &factory);
def("consumer", &consumer);
}
由于模块声明指定>>> from example import *
>>> x = factory()
>>> type(x)
<class 'example.A'>
>>> consumer(x)
The value of object is 42
>>>
是Base
的基类,Boost.Python能够解析从A
返回到factory()
的类型。< / p>
答案 1 :(得分:3)
是的,有。你必须声明自己的“from-python”转换器。它在boost.python文档中有一点含糊地解释(请看this answer in the FAQ),但你会在网上找到教程,比如this one。这是一个完整的示例,基于您执行所需的初始输入:
#include <boost/python.hpp>
#include <boost/make_shared.hpp>
class Base {
public:
virtual ~Base() {}
};
class A: public Base {
public:
A(int v): _value(v) {}
int _value;
};
boost::shared_ptr<Base> factory() {
return boost::make_shared<A>(27);
}
void consumer(boost::shared_ptr<A> a) {
std::cout << "The value of object is " << a->_value << std::endl;
}
struct a_from_base {
typedef typename boost::shared_ptr<A> container_type;
// Registers the converter for boost.python
a_from_base() {
boost::python::converter::registry::push_back(&convertible, &construct,
boost::python::type_id<container_type>());
}
// Determines convertibility: checks if a random
// object is convertible to boost::shared_ptr<A>
static void* convertible(PyObject* ptr) {
boost::python::object object(boost::python::handle<>(boost::python::borrowed(ptr)));
boost::python::extract<boost::shared_ptr<Base> > checker(object);
if (checker.check()) { //is Base
boost::shared_ptr<Base> base = checker();
if (boost::dynamic_pointer_cast<A>(base)) return ptr; //is A
}
return 0; //is not A
}
// Runs the conversion (here we know the input object *is* convertible)
static void construct(PyObject* ptr, boost::python::converter::rvalue_from_python_stage1_data* data) {
// This is some memory allocation black-magic that is necessary for bp
void* storage = ((boost::python::converter::rvalue_from_python_storage<container_type>*)data)->storage.bytes;
new (storage) container_type();
data->convertible = storage;
container_type& result = *((container_type*)storage); //< your object
// The same as above, but this time we set the provided memory
boost::python::object object(boost::python::handle<>(boost::python::borrowed(ptr)));
boost::shared_ptr<Base> base = boost::python::extract<boost::shared_ptr<Base> >(object);
result = boost::dynamic_pointer_cast<A>(base);
}
};
// Your boost python module: compile it and run your test
// You should get "The value of object is 27".
BOOST_PYTHON_MODULE(convertible) {
using namespace boost::python;
class_<Base, boost::shared_ptr<Base>, boost::noncopyable>("Base", no_init);
class_<A, boost::shared_ptr<A>, bases<Base>, boost::noncopyable>("A", no_init);
a_from_base();
def("factory", &factory);
def("consumer", &consumer);
}
您可以通过为您的班级B
编写另一个from-python转换器来扩展此示例,或者只使用上面结构的模板来容纳Base
的所有子级。
答案 2 :(得分:3)
正如所提出的那样,使用自动(或手动)from-python转换器无法解决您的问题,如in this thread所述。您将需要对其进行修补,模拟动态类型行为,以便它可以像您期望的那样工作。我假设,由于您传递的是非const引用,因此需要在consumer()
方法中进行更改。
这是一个完整的例子。 C ++ plumbings需要知道在Python中引发Base
或TypeError
的所有可能派生。动手:
#include <boost/python.hpp>
#include <boost/make_shared.hpp>
class Base {
public:
Base(int value) : value_(value) {}
virtual ~Base() {}
int value() const { return value_; };
void value(int value) { value_ = value; }
private:
int value_;
};
class A : public Base {
public:
A(int value): Base(value) {}
};
class B : public Base {
public:
B(int value): Base(value) {}
};
class C : public Base {
public:
C(int value): Base(value) {}
};
boost::shared_ptr<Base> factory(boost::python::str choose) {
if (choose == "a") return boost::make_shared<A>(1);
else if (choose == "b") return boost::make_shared<B>(10);
else return boost::make_shared<C>(100);
}
void consumer_a(boost::shared_ptr<A>& a) {
std::cout << "The value of object was " << a->value() << std::endl;
a = boost::make_shared<A>(a->value()+1);
std::cout << "The new value of object is " << a->value() << std::endl;
}
void consumer_b(boost::shared_ptr<B>& b) {
std::cout << "The value of object is " << b->value() << std::endl;
b = boost::make_shared<B>(b->value()+1);
std::cout << "The new value of object is " << b->value() << std::endl;
}
void consumer_python(boost::shared_ptr<Base>& base) {
//try the first one
boost::shared_ptr<A> a = boost::dynamic_pointer_cast<A>(base);
if (a) {
consumer_a(a);
base = a; ///< don't forget the assignment here
return;
}
//try the second one
boost::shared_ptr<B> b = boost::dynamic_pointer_cast<B>(base);
if (b) {
consumer_b(b);
base = b; ///< don't forget the assignment here
return;
}
//we leave C uncovered to see what happens ;-)
//if you get here, you can raise an exception for python
PyErr_Format(PyExc_TypeError, "Input type is neither A or B");
throw boost::python::error_already_set();
}
//notice you don't even need to declare a binding for A, B or C
BOOST_PYTHON_MODULE(example) {
using namespace boost::python;
class_<Base, boost::shared_ptr<Base>, boost::noncopyable>("Base", no_init);
def("factory", &factory);
def("consumer", &consumer_python);
}
编译后,您可以运行此脚本并查看它是否正常工作:
import example
print "Creating object of type A..."
a = example.factory("a")
print "Consuming A twice..."
example.consumer(a)
example.consumer(a)
print "Creating object of type B..."
b = example.factory("b")
print "Consuming B twice..."
example.consumer(b)
example.consumer(b)
print "Creating object of type C..."
c = example.factory("c")
print "Trying to consume (uncovered) C..."
example.consumer(c)
输出应该是这样的:
Creating object of type A...
Consuming A twice...
The value of object was 1
The new value of object is 2
The value of object was 2
The new value of object is 3
Creating object of type B...
Consuming B twice...
The value of object is 10
The new value of object is 11
The value of object is 11
The new value of object is 12
Creating object of type C...
Trying to consume (uncovered) C...
Traceback (most recent call last):
File "test.py", line 25, in <module>
example.consumer(c)
TypeError: Input type is neither A or B