Boost (a collection of C++ libraries) provides excellent tools for writing Python extension modules in C++. However building these modules for Python3 has some issues. This blog suggests a workaround
Boost (a collection of C++ libraries) provides excellent tools for writing Python extension modules in C++. However building these modules for Python3 has some issues. This blog suggests a workaround.
Motivation
Why build Python extension modules in C++? There are at least two use cases. The first one is that there is a C++ library that you would like to port to Python. The second reason is that a pure Python library is slow or too large that further extension becomes difficult and hence a C++ port of the library is desired.
Boost Python
Boost (www.boost.org) is a collection of C++ libraries that have in many
instances been incorporated into standard C++. It provides a C++ framework that makes building Python extension modules easy. Here is an example from Boost
char const* greet()
{
return "hello, world";
}
BOOST_PYTHON_MODULE(hello_ext)
{
using namespace boost::python;
def("greet", greet);
}
The def method exposes the greet function as “greet“. Calling the method from Python is simple as shown in the following code from Boost:
import hello_ext
print(hello_ext.greet())
Exposing C++ classes is easy too:
namespace { // Avoid cluttering the global namespace.
// A friendly class.
class hello
{
public:
hello(const std::string& country) { this->country = country; }
std::string greet() const { return "Hello from " + country; }
private:
std::string country;
};
// A function taking a hello object as an argument.
std::string invite(const hello& w) {
return w.greet() + "! Please come soon!";
}
}
BOOST_PYTHON_MODULE(extending)
{
using namespace boost::python;
class_<hello>("hello", init<std::string>())
// Add a regular member function.
.def("greet", &hello::greet)
// Add invite() as a member of hello!
.def("invite", invite)
;
// Also add invite() as a regular function to the module.
def("invite", invite);
}
Here the class hello is exposed to Python via class_<hello>. The individual methods in the class are exposed by taking the member function pointer. Notice also a function invite is exposed to Python both as member function and an external function accepting an object reference.
Calling the C++ object method is shown below:
from extending import *
hi = hello('California')
hi.greet()
invite(hi)
Creating a factory method that returns an object is more involved as we have to take care of object lifetimes. However if the factory returns an object that is independent of other objects with regards to its life time then the method can return an object. Of the method must be declared within the Module definition as well.
Issues with Boost Python
To build Boost Python we need to download the latest release of Boost. Run bootstrap. Then build the desired libraries using ./b2 with the with-<library> option. In our case we would substitute python for <library>. This does not work if both Python 2 and Python 3 are available because by default Boost constructs the build system for Python 2.
Users at StackOverflow suggest a number of options. I found Maxim Dolgov’s suggestion to be the most appropriate which is to run bootstrap with with-python=python3 option before building the libraries. That way we could specify the specific Python virtual environment if we wanted to.
Having done that and then running b2 with-python we generate the boost_python3x library where x refers to the minor version number. Now if we cd to libs/python/examples/tutorial folder and then run /path/to/b2 we will get build errors. To see what is going on it is best to run /path/to/b2 -a -n. The -a option builds everything and -n option shows the commands that would be executed. The first build error without the -n option will be about missing boost headers. This is easily fixed by typing the build command on the shell prompt with the -I option to search additional include folders. Running b2 again will result in libraries not found error. Here we need to both include the library paths with -L and modify the library names. Boost builds libboost_python3x whereas the b2 tries to link with libboost_python. In addition it does not link with the libpython3.7m. These operations have to be done manually. The following makefile might help with these issues
CFLAGS = -I/home/joe/boost_1_76_0 -I/usr/include/python3.7
LFLAGS = -L/home/joe/boost_1_76_0/stage/lib
all : embedding extending.so
%.o : %.cpp
"g++" -fPIC -O0 -fno-inline -Wall $(CFLAGS) -c $< -o $@
extending.so : extending.o
"g++" -o $@ -Wl,-h -Wl,$@ -shared -Wl,--start-group $< $(LFLAGS) -lboost_python37 -ldl -lpthread -lutil -lpython3.7m -Wl,--end-group -fPIC
embedding : embedding.o
"g++" -o $@ -Wl,--start-group $< $(LFLAGS) -lboost_python37 -ldl -lpthread -lutil -lpython3.7m -Wl,--end-group -fPIC
clean:
rm embedding embedding.o extending.so extending.o
The Python version number and LDFLAGS and CFLAGS need to be modified for local conditions.
Conclusion
While Boost provides nice tools for building Python extension modules, additional work is required for Python 3. In this blog I covered the basics of building such modules. While a lot can be done with these basics, there is more to it than what I have addressed. Check out the Boost documentation for details.