2.4. Wrap C++ with Boost.Python

Boost is a high-quality, widely-used, open-source C++ library. Boost.Python is one component project that provides a comprehensive wrapping capabilities between C++ and Python. By using Boost.Python, one can easily create a Python extension module with C++.

2.4.1. Create a Python Extension

The basic and the most important feature of Boost.Python is to help writing Python extension modules by using C++.

This is our first Python extension module by Boost.Python; call it zoo.cpp:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/*
 * This inclusion should be put at the beginning.  It will include <Python.h>.
 */
#include <boost/python.hpp>
#include <string>

/*
 * This is the C++ function we write and want to expose to Python.
 */
const std::string hello() {
    return std::string("hello, zoo");
}

/*
 * This is a macro Boost.Python provides to signify a Python extension module.
 */
BOOST_PYTHON_MODULE(zoo) {
    // An established convention for using boost.python.
    using namespace boost::python;

    // Expose the function hello().
    def("hello", hello);
}

// vim: set ai et nu sw=4 ts=4 tw=79:

It simply return a string from C++ to Python. Boost.Python will do all the conversion and interfacing for us:

1
2
3
4
5
6
7
import zoo
# In zoo.cpp we expose hello() function, and it now exists in the zoo module.
assert 'hello' in dir(zoo)
# zoo.hello is a callable.
assert callable(zoo.hello)
# Call the C++ hello() function from Python.
print zoo.hello()

Running the above script (call it visit_zoo.py) will get:

hello, zoo

The following makefile will help us build the module (and run it):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
CC = g++
PYLIBPATH = $(shell python-config --exec-prefix)/lib
LIB = -L$(PYLIBPATH) $(shell python-config --libs) -lboost_python
OPTS = $(shell python-config --include) -O2

default: zoo.so
	@python ./visit_zoo.py

zoo.so: zoo.o
	$(CC) $(LIB) -Wl,-rpath,$(PYLIBPATH) -shared $< -o $@

zoo.o: zoo.cpp Makefile
	$(CC) $(OPTS) -c $< -o $@

clean:
	rm -rf *.so *.o

.PHONY: default clean

2.4.2. Wrap a Class

Expose a class Animal from C++ to Python:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
/*
 * This inclusion should be put at the beginning.  It will include <Python.h>.
 */
#include <boost/python.hpp>
#include <cstdint>
#include <string>
#include <vector>
#include <boost/utility.hpp>
#include <boost/shared_ptr.hpp>

/*
 * This is the C++ function we write and want to expose to Python.
 */
const std::string hello() {
    return std::string("hello, zoo");
}

/*
 * Create a C++ class to represent animals in the zoo.
 */
class Animal {
public:
    // Constructor.  Note no default constructor is defined.
    Animal(std::string const & in_name): m_name(in_name) {}
    // Copy constructor.
    Animal(Animal const & in_other): m_name(in_other.m_name) {}
    // Copy assignment.
    Animal & operator=(Animal const & in_other) {
        this->m_name = in_other.m_name;
        return *this;
    }

    // Utility method to get the address of the instance.
    uintptr_t get_address() const {
        return reinterpret_cast<uintptr_t>(this);
    }

    // Getter of the name property.
    std::string get_name() const {
        return this->m_name;
    }
    // Setter of the name property.
    void set_name(std::string const & in_name) {
        this->m_name = in_name;
    }

private:
    // The only property: the name of the animal.
    std::string m_name;
};

/*
 * This is a macro Boost.Python provides to signify a Python extension module.
 */
BOOST_PYTHON_MODULE(zoo) {
    // An established convention for using boost.python.
    using namespace boost::python;

    // Expose the function hello().
    def("hello", hello);

    // Expose the class Animal.
    class_<Animal>("Animal",
        init<std::string const &>())
        .def("get_address", &Animal::get_address)
        .add_property("name", &Animal::get_name, &Animal::set_name)
    ;
}

// vim: set ai et nu sw=4 ts=4 tw=79:

The script changes to:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
import zoo
# In zoo.cpp we expose hello() function, and it now exists in the zoo module.
assert 'hello' in dir(zoo)
# zoo.hello is a callable.
assert callable(zoo.hello)
# Call the C++ hello() function from Python.
print zoo.hello()

# Create an animal.
animal = zoo.Animal("dog")
# The Python object.
print animal
# Use the exposed method to show the address of the C++ object.
print "The C++ object is at 0x%016x" % animal.get_address()
# Use the exposed property accessor.
print "I see a \"%s\"" % animal.name
animal.name = "cat"
print "I see a \"%s\"" % animal.name

The output is:

hello, zoo
<zoo.Animal object at 0x102437890>
The C++ object is at 0x00007fb0c860ac20
I see a "dog"
I see a "cat"

2.4.3. Provide Docstrings

2.4.4. Share Instances between C++ and Python

2.4.5. Method Overloading

2.4.7. Call Back to Python