Python explicit type needs to be promoted
I have a hybrid system (C ++, boost python). There is a very simple hierarchy in my C ++ code
class Base{...}
class A : public Base{...}
class B : public Base{...}
2 more ways (in C ++)
smart_ptr<Base> factory() //this produce instances of A and B
void consumer(smart_ptr<A>& a) //this consumes instance of A
In python code, I instantiate A with factory
and try to call the consumer method:
v = factory() #I'm pretty sure that it is A instance
consumer(v)
It makes absolutely sense that I have an exception:
The Python argument types of the consumer (Base) did not match the C ++ signature: consumer (class A {lvalue})
This is because there is no way to tell Boost that some transforms should be there.
Is there a way to specify dynamic casting behavior? Thank you in advance.
source to share
When it comes down to it boost::shared_ptr
, Boost.Python usually provides the functionality you need. In this particular case, there is no need to explicitly provide custom converters to_python
as long as the module declaration defines what is Base
supported boost::shared_ptr<Base>
and Boost.Python tells it A
inherits from Base
.
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);
}
Boost.Python does not currently support custom lvalue converters as it requires changes to the core library. Therefore, the function consumer
must either take a value boost:shared_ptr<A>
by value or a constant reference. Any of the following signatures should work:
void consumer(boost::shared_ptr<A> a)
void consumer(const boost::shared_ptr<A>& a)
Here's a complete example:
#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);
}
And usage:
>>> from example import *
>>> x = factory()
>>> type(x)
<class 'example.A'>
>>> consumer(x)
The value of object is 42
>>>
Because the module declaration states that it Base
was the base class for A
, Boost.Python was able to resolve the type returned from factory()
to example.A
.
source to share
Yes there is. You must declare your own "from-python" converter. This is vaguely explained in the boost.python documentation (see this answer in the FAQ ), but you will find tutorials online, such as this one . Here's a complete example based on original input that does what you want:
#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);
}
You can extend this example by writing another python converter for your class, B
or just the template that was created above to accommodate all children Base
.
source to share
As expected, your problem cannot be solved using the automatic (or manual) converters from python as described in this thread . You will need to fix this by emulating the behavior of the dynamic type in order for it to work the way you expect Python to. I am assuming that since you are passing in a non-const reference, you will need to modify it inside the method consumer()
.
Here's a complete working example. C ++ plumbings need to be aware that all possible differentiations Base
or TypeError
arises in Python. Practical:
#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);
}
Once compiled, you can run this script and see how it works:
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)
The result should look something like this:
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
source to share