简体   繁体   中英

Exposing virtual member functions from C++ to Python using boost::python

I try to expose two different classes to python, but I don't get it to compile. I tried to follow the boost::python example, which works quite well. But if I try to write the wrapper classes for my classes it doesn't work. I have provided two minimal examples below:

struct Base
{
    virtual ~Base() {}
    virtual std::unique_ptr<std::string> f() = 0;
};

struct BaseWrap : Base, python::wrapper<Base>
{
    std::unique_ptr<std::string> f()
    {
        return this->get_override("f")();
    }
};

and

struct Base
{
    virtual ~Base() {}
    virtual void f() = 0;
};

struct BaseWrap : Base, python::wrapper<Base>
{
    void f()
    {
        return this->get_override("f")();
    }
};

The first one does not compile because of the unique pointer(I think boost::python does not use unique pointers?) and the second example complains about the return statement inside the void function. Can someone help me how to solve this problems?

The examples are failing to compile because:

  • The first example attempts to convert an unspecified type (the return type of override::operator() ) to an incompatible type. In particular, Boost.Python does not currently support std::unique_ptr , and hence will not convert to it.
  • The second example attempts to return the unspecified type mentioned above when the calling function declares that it returns void .

From a Python perspective, strings are immutable, and attempting to transferring ownership of a string from Python to C++ violates semantics. However, one could create a copy of a string within C++, and pass ownership of the copied string to C++. For example:

std::unique_ptr<std::string> BaseWrap::f()
{
  // This could throw if the Python method throws or the Python
  // method returns a value that is not convertible to std::string.
  std::string result = this->get_override("f")();

  // Adapt the result to the return type.
  return std::unique_ptr<std::string>(new std::string(result));
}

The object returned from this->get_override("f")() has an unspecified type, but can be used to convert to C++ types. The invocation of the override will throw if Python throws, and the conversion to the C++ type will throw if the object returned from Python is not convertible to the C++ type.


Here is a complete example demonstrating two ways to adapt the returned Python object to a C++ object. As mentioned above, the override conversion can be used. Alternatively, one can use boost::python::extract<> , allowing one to check if the conversion will fail before performing the conversion:

#include <memory> // std::unique_ptr
#include <boost/algorithm/string.hpp> // boost::to_upper_copy
#include <boost/python.hpp>

struct base
{
  virtual ~base() {}
  virtual std::unique_ptr<std::string> perform() = 0;
};

struct base_wrap : base, boost::python::wrapper<base>
{
  std::unique_ptr<std::string> perform()
  {
    namespace python = boost::python;
    // This could throw if the Python method throws or the Python
    // method returns a value that is not convertible to std::string.
    std::string result = this->get_override("perform")();

    // Alternatively, an extract could be used to defer extracting the
    // result.
    python::object method(this->get_override("perform"));
    python::extract<std::string> extractor(method());

    // Check that extractor contains a std::string without throwing.
    assert(extractor.check());

    // extractor() would throw if it did not contain a std::string.
    assert(result == extractor());

    // Adapt the result to the return type.
    return std::unique_ptr<std::string>(new std::string(result));
  }
};

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::def("make_upper", +[](base* object) {
    auto result = object->perform(); // Force dispatch through base_wrap.
    assert(result);
    return boost::to_upper_copy(*result);
  });
}

Interactive usage:

>>> import example
>>> class Derived(example.Base):
...     def perform(self):
...         return "abc"
...        
>>> derived = Derived()
>>> assert("ABC" == example.make_upper(derived))

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM