Custom Boost.Python Converter

I have a class taking a vector as a parameter (contents of a binary file).

I would like to convert the python type 'str' to an unsigned char vector, but only for one of the methods of my class.

BOOST_PYTHON_MODULE(hello) { 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) } 

Using a custom converter seems to be what I need, but if I change my boost :: python :: converter :: registry, it will change for all my printChar calls and all python methods that pass a string, since the parameter will be converted to vector,

How can I register a converter for each method?

+4
source share
1 answer

There are two approaches to this problem:

  • Export the helper function as Hello.storeFile , which takes boost::python::str , builds std::vector<unsigned char> from a string, and delegates C ++ member functions Hello::storeFile .
  • Write a custom converter. Although transducers cannot be registered based on each function, they are pretty well embraced by not performing 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 the python string and std::vector<unsigned char> will only happen for Hello.storeFile .

 void Hello_storeFile(Hello& self, boost::python::str str) { std::cout << "Hello_storeFile" << std::endl; // Obtain a handle to the string. const char* 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

Transmitter registration consists of three parts:

  • A function that checks if PyObject convertible. Returning NULL means PyObject cannot use the registered converter.
  • A build function that builds a C ++ type from PyObject . This function is called only if converter(PyObject) does not return NULL .
  • The type of C ++ that will be created.

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

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

Here is a complete example. I decided to make a universal converter to allow the creation of several types from a python string. Thanks to the support of the chain, it should have the same feeling as other types of Boost.Python.

 #include <iostream> #include <list> #include <string> #include <vector> #include <boost/foreach.hpp> #include <boost/python.hpp> class Hello { public: void printChar(const std::string& str) { std::cout << "printChar: " << str << std::endl; } void storeFile(const std::vector<unsigned char>& data) { std::cout << "storeFile: " << data.size() << ": "; BOOST_FOREACH(const unsigned char& c, data) std::cout << c; std::cout << std::endl; } }; /// @brief Type that allows for conversions of python strings to // vectors. struct pystring_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. static void* convertible(PyObject* object) { return PyString_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. /// Ie Container(begin, end) template <typename Container> static void construct( 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 memory block, and assign // its handle to the converter convertible variable. The C++ // container is populated by passing the begin and end iterators of // the python object to the container constructor. const char* 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<unsigned char> >() .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 an example of use:

 >>> 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 C ++ converters and containers, consider this answer.

+12
source

All Articles