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.

+3


source to share


3 answers


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

.

+4


source


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

.

+3


source


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

      

+3


source







All Articles