Advanced C++ / Python integration with Boost.Python (Part 3)

In this final part of the series I’m going to look at one further way we can use Boost.Python to help when using a C++ class within Python.

In the previous part we built a wrapper for a C++ class, exposing a number of class methods to Python.

However, in Python, it’s quite typical for a class to use properties as well as methods.

So to begin – let’s take a moment to look at properties within the context of classes in Python.

For example, if we had a Python class which looks like this:

class simple:
    def __init__ (self):
        self._data = None

    def set_data(self, data):
        self._data = data

    def get_data(self):
        return self._data

We could then use it, something like this:

import example

s = example.simple()
s.set_data(7)

print(s.get_data())

Whilst this works, we might reasonably say that the necessity to use getter and setter functions directly is not especially Pythonic – as it’s not how most built-in objects behave.

Instead we’d expect to be able to use it more like this:

import example

s=example.simple()

s.data = 7

print(s.data)

We can do exactly this; if we use properties…

We just need to make a couple of changes to our Python code to enable this.

class simple:
    def __init__ (self):
        self._data = None

    @property
    def data(self):
        return self._data

    @data.setter
    def data(self, data):
        self._data = data

Note that substantively the code is unchanged; but we have renamed our two methods (giving them both the same name) and we’ve added two decorators – setting up the property (and thus implicitly the getter for that) and the explicit setup for the setter method.

Running this version in conjunction with the second usage example, we have a much more Pythonic implementation.

So the question is – can we do this when we’re wrapping a C++ class, with Boost.Python?

Well, of course the answer is yes!

Last time around we finished up with a Python class created using this call of the BOOST_PYTHON_MODULE macro.

BOOST_PYTHON_MODULE(example)
{
  namespace python = boost::python;
  python::class_<ExampleClass>("ExampleClass")
    .def("print", &ExampleClass::print_state)
    .def("exporter", &example_class_export_wrap)
    .def("set_data", &ExampleClass::set_data)
    .def("importer", &example_class_import_wrap)
    ;
}

We’ll leave the explicit way of setting the data with three discrete values (via set_data()) as-is, as that’s a slightly different use-case; but wouldn’t it be nice if we could use the import and export methods as the setter and getter of a bytes property?

It turns out that doing this is trivially easy. All we do is remove the .def lines for the importer and exporter methods; and replace them with one .add_property() line…

BOOST_PYTHON_MODULE(example)
{
  namespace python = boost::python;
  python::class_<ExampleClass>("ExampleClass")
    .def("print", &ExampleClass::print_state)
    .def("set_data", &ExampleClass::set_data)
    .add_property("bytes", &example_class_export_wrap, &example_class_import_wrap)
    ;
}

Now we can use the new bytes property from within Python by using:

...
e.bytes = pybuf.decode()
...
t = e.bytes
...

That concludes my brief introduction on how to use Boost.Python to wrap your existing C++ code for use in Python. I hope you’ve found it useful or interesting.

Leave a Reply

Your email address will not be published. Required fields are marked *