Skip to content Skip to sidebar Skip to footer

Boost.python Custom Converter

I have a class taking a vector as parameter (a binary file content). I would like to convert python 'str' type into vector of unsigned char but only for one of my class method. BOO

Solution 1:

There are two approaches to this problem:

  • Export a helper function as Hello.storeFile that accepts boost::python::str, constructs std::vector<unsigned char> from the string, and delegates to the C++ Hello::storeFile member function.
  • Write a custom converter. While converters cannot be registered on a per-function basis, they are fairly well scoped as to not perform any unintended conversions. This approach often provides more reusability.

Helper Function

Using a helper function will not affect any other exported function. Thus, the conversion between a python string and std::vector<unsigned char> will only occur for Hello.storeFile.

void Hello_storeFile(Hello& self, boost::python::strstr)
{
  std::cout << "Hello_storeFile" << std::endl;
  // Obtain a handle to the string.constchar* begin = PyString_AsString(str.ptr());
  // Delegate to Hello::storeFile().self.storeFile(std::vector<unsigned char>(begin, begin + len(str)));
}

...

BOOST_PYTHON_MODULE(hello)
{
  namespace python = boost::python;

  python::class_<Hello>("Hello")
    // This method takes a string as parameter and print it
    .def("printChar", &Hello::printChar)
    // This method takes a vector<unsigned char> parameter
    .def("storeFile", &Hello_storeFile)
    ;
}

Custom Converter

A converter registration has three parts:

  • A function that checks if a PyObject is convertible. A return of NULL indicates that the PyObject cannot use the registered converter.
  • A construct function that constructs the C++ type from a PyObject. This function will only be called if converter(PyObject) does not return NULL.
  • The C++ type that will be constructed.

Therefore, for a given C++ type, if converter(PyObject) returns a non-NULL value, then construct(PyObject) will create the C++ type. The C++ type serves as a key into the registry, so Boost.Python should not perform unintended conversions.

In context of the question, we want a converter for std::vector<unsigned char> where converter(PyObject) returns non-NULL if PyObject is a PyString, and converter(PyObject) will use PyObject to create and populate std::vector<unsigned char>. This conversion will only occur if for exported C++ functions that have a std::vector<unsigned char> (or a const reference) parameter and the argument provided from python is a string. Therefore, this custom converter will not affect exported functions that have std::string parameters.

Here is a complete example. I have opted to make the converter generic to allow multiple types to be constructable from a python string. With its chaining support, it should have the same feel as other Boost.Python types.

#include<iostream>#include<list>#include<string>#include<vector>#include<boost/foreach.hpp>#include<boost/python.hpp>classHello
{
public:
  voidprintChar(const std::string& str){
    std::cout << "printChar: " << str << std::endl;
  }

  voidstoreFile(const std::vector<unsignedchar>& data){
    std::cout << "storeFile: " << data.size() << ": ";
    BOOST_FOREACH(constunsignedchar& c, data)
      std::cout << c;
    std::cout << std::endl;
  }
};

/// @brief Type that allows for conversions of python strings to//         vectors.structpystring_converter
{

  /// @note Registers converter from a python interable type to the///       provided type.template <typename Container>
  pystring_converter&
  from_python(){
    boost::python::converter::registry::push_back(
      &pystring_converter::convertible,
      &pystring_converter::construct<Container>,
      boost::python::type_id<Container>());
    return *this;
  }

  /// @brief Check if PyObject is a string.staticvoid* convertible(PyObject* object){
    returnPyString_Check(object) ? object : NULL;
  }

  /// @brief Convert PyString to Container.////// Container Concept requirements://////   * Container::value_type is CopyConstructable from char.///   * Container can be constructed and populated with two iterators.///     I.e. Container(begin, end)template <typename Container>
  staticvoidconstruct(
    PyObject* object,
    boost::python::converter::rvalue_from_python_stage1_data* data){
    namespace python = boost::python;
    // Object is a borrowed reference, so create a handle indicting it is// borrowed for proper reference counting.
    python::handle<> handle(python::borrowed(object));

    // Obtain a handle to the memory block that the converter has allocated// for the C++ type.typedef python::converter::rvalue_from_python_storage<Container>
                                                                 storage_type;
    void* storage = reinterpret_cast<storage_type*>(data)->storage.bytes;

    // Allocate the C++ type into the converter's memory block, and assign// its handle to the converter's convertible variable.  The C++// container is populated by passing the begin and end iterators of// the python object to the container's constructor.constchar* begin = PyString_AsString(object);
    data->convertible = new (storage) Container(
      begin,                          // begin
      begin + PyString_Size(object)); // end
  }
};

BOOST_PYTHON_MODULE(hello)
{
  namespace python = boost::python;

  // Register PyString conversions.pystring_converter()
    .from_python<std::vector<unsignedchar> >()
    .from_python<std::list<char> >()
    ;

  python::class_<Hello>("Hello")
    // This method takes a string as parameter and print it
    .def("printChar", &Hello::printChar)
    // This method takes a vector<unsigned char> parameter
    .def("storeFile", &Hello::storeFile)
    ;
}

And the example usage:

>>>from hello import Hello>>>h = Hello()>>>h.printChar('abc')
printChar: abc
>>>h.storeFile('def')
storeFile: 3: def
>>>h.storeFile([c for c in'def'])
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
Boost.Python.ArgumentError: Python argument types in
    Hello.storeFile(Hello, list)
did not match C++ signature:
    storeFile(Hello {lvalue}, std::vector<unsigned char, 
                                          std::allocator<unsigned char> >)

For more on custom converters and C++ containers, consider reading this answer.

Post a Comment for "Boost.python Custom Converter"