Python Programming/Extending with C++

From Wikibooks, open books for an open world
Jump to: navigation, search

There are different ways to extend Python:

  • In plain C, using Python.h
  • Using Swig
  • Using Boost.Python, optionally with Py++ preprocessing
  • Using Cython.

This page describes Boost.Python. Before the emergence of Cython, it was the most comfortable way of writing C++ extension modules.

Boost.Python comes bundled with the Boost C++ Libraries. To install it on an Ubuntu system, you might need to run the following commands

$ sudo apt-get install libboost-python-dev 
$ sudo apt-get install python-dev

A Hello World Example[edit]

The C++ source code (hellomodule.cpp)[edit]

#include <iostream>
 
using namespace std;
 
void say_hello(const char* name) {
    cout << "Hello " <<  name << "!\n";
}
 
#include <boost/python/module.hpp>
#include <boost/python/def.hpp>
using namespace boost::python;
 
BOOST_PYTHON_MODULE(hello)
{
    def("say_hello", say_hello);
}

setup.py[edit]

#!/usr/bin/env python
 
from distutils.core import setup
from distutils.extension import Extension
 
setup(name="PackageName",
    ext_modules=[
        Extension("hello", ["hellomodule.cpp"],
        libraries = ["boost_python"])
    ])

Now we can build our module with

python setup.py build

The module `hello.so` will end up in e.g `build/lib.linux-i686-2.4`.

Using the extension module[edit]

Change to the subdirectory where the file `hello.so` resides. In an interactive python session you can use the module as follows.

>>> import hello
>>> hello.say_hello("World")
Hello World!

An example with CGAL[edit]

Some, but not all, functions of the CGAL library have already Python bindings. Here an example is provided for a case without such a binding and how it might be implemented. The example is taken from the CGAL Documentation.

// test.cpp
using namespace std;
 
/* PYTHON */
#include <boost/python.hpp>
#include <boost/python/module.hpp>
#include <boost/python/def.hpp>
namespace python = boost::python;
 
/* CGAL */
#include <CGAL/Cartesian.h>
#include <CGAL/Range_segment_tree_traits.h>
#include <CGAL/Range_tree_k.h>
 
typedef CGAL::Cartesian<double> K;
typedef CGAL::Range_tree_map_traits_2<K, char> Traits;
typedef CGAL::Range_tree_2<Traits> Range_tree_2_type;
 
typedef Traits::Key Key;
typedef Traits::Interval Interval;
 
Range_tree_2_type *Range_tree_2 = new Range_tree_2_type;
 
void create_tree()   {
 
  typedef Traits::Key Key;                
  typedef Traits::Interval Interval;    
 
  std::vector<Key> InputList, OutputList;
  InputList.push_back(Key(K::Point_2(8,5.1), 'a'));
  InputList.push_back(Key(K::Point_2(1.0,1.1), 'b'));
  InputList.push_back(Key(K::Point_2(3,2.1), 'c'));
 
  Range_tree_2->make_tree(InputList.begin(),InputList.end());
  Interval win(Interval(K::Point_2(1,2.1),K::Point_2(8.1,8.2)));
  std::cout << "\n Window Query:\n";
  Range_tree_2->window_query(win, std::back_inserter(OutputList));
  std::vector<Key>::iterator current=OutputList.begin();
  while(current!=OutputList.end()){
      std::cout << "  " << (*current).first.x() << "," << (*current).first.y()
           << ":" << (*current).second << std::endl;
      current++;
    }
  std::cout << "\n Done\n";
}
 
void initcreate_tree() {;}
 
using namespace boost::python;
BOOST_PYTHON_MODULE(test)
{
    def("create_tree", create_tree, "");
}
// setup.py
#!/usr/bin/env python
 
from distutils.core import setup
from distutils.extension import Extension
 
setup(name="PackageName",
    ext_modules=[
        Extension("test", ["test.cpp"],
        libraries = ["boost_python"])
    ])

We then compile and run the module as follows:

$ python setup.py build
$ cd build/lib*
$ python
>>> import test
>>> test.create_tree()
Window Query:
 3,2.1:c
 8,5.1:a
Done
>>>

Handling Python objects and errors[edit]

One can also handle more complex data, e.g. Python objects like lists. The attributes are accessed with the extract function executed on the objects "attr" function output. We can also throw errors by telling the library that an error has occurred and returning. In the following case, we have written a C++ function called "afunction" which we want to call. The function takes an integer N and a vector of length N as input, we have to convert the python list to a vector of strings before calling the function.

#include <vector>
using namespace std;
 
void _afunction_wrapper(int N, boost::python::list mapping) {
 
    int mapping_length = boost::python::extract<int>(mapping.attr("__len__")());
    //Do Error checking, the mapping needs to be at least as long as N 
    if (mapping_length < N) {
        PyErr_SetString(PyExc_ValueError,
            "The string mapping must be at least of length N");
        boost::python::throw_error_already_set();
        return;
    }
 
    vector<string> mystrings(mapping_length);
    for (int i=0; i<mapping_length; i++) {
        mystrings[i] = boost::python::extract<char const *>(mapping[i]);
    }
 
   //now call our C++ function
   _afunction(N, mystrings);
 
}
 
using namespace boost::python;
BOOST_PYTHON_MODULE(c_afunction)
{
    def("afunction", _afunction_wrapper);
}