Embedding Python in C++ Applications with boost::python: Part 4

In Part 2 of this ongoing tutorial, I introduced code for parsing Python exceptions from C++. In Part 3, I implemented a simple configuration parsing class utilizing the Python ConfigParser module. As part of that implementation, I mentioned that for a project of any scale, one would want to catch and deal with Python exceptions within the class, so that clients of the class wouldn’t have to know about the details of Python. From the perspective of a caller, then, the class would be just like any other C++ class.

The obvious way of handling the Python exceptions would be to handle them in each function. For example, the get function of the C++ ConfigParser class we created would become:

std::string ConfigParser::get(const std::string &attr, const std::string &section){
    try{
        return py::extract(conf_parser_.attr("get")(section, attr));
    }catch(boost::python::error_already_set const &){
        std::string perror_str = parse_python_exception();
        throw std::runtime_error("Error getting configuration option: " + perror_str);
    }
}

The error handling code remains the same, but now the main function becomes:

int main(){
    Py_Initialize();
    try{
        ConfigParser parser;
        parser.parse_file("conf_file.1.conf");
        ...
        // Will raise a NoOption exception 
         cout << "Proxy host: " << parser.get("ProxyHost", "Network") << endl;
    }catch(exception &e){
        cout << "Here is the error, from a C++ exception: " << e.what() << endl;
    }
}

When the Python exception is raised, it will be parsed and repackaged as a std::runtime_error, which is caught at the caller and handled like a normal C++ exception (i.e. without having to go through the parse_python_exception rigmarole). For a project that only has a handful of functions or a class or two utilizing embedded Python, this will certainly work. For a larger project, though, one wants to avoid the large amount of duplicated code and the errors it will inevitably bring.

For my implementation, I wanted to always handle the the errors in the same way, but I needed a way to call different functions with different signatures. I decided to leverage another powerful area of the boost library: the functors library, and specifically boost::bind and boost::function. boost::function provides functor class wrappers, and boost::bind (among other things) binds arguments to functions. The two together, then, enable the passing of functions and their arguments that can be called at a later time. Just what the doctor ordered!

To utilize the functor, the function needs to know about the return type. Since we're wrapping functions with different signatures, a function template does the trick nicely:

template <class return_type>
return_type call_python_func(boost::function<return_type ()> to_call, const std::string &error_pre){
    std::string error_str(error_pre);

    try{
        return to_call();
    }catch(boost::python::error_already_set const &){
        error_str = error_str + parse_python_exception();
        throw std::runtime_error(error_str);
    }
}

This function takes the functor object for a function calling boost::python functions. Each function that calls boost::python code will now be split into two functions: the private core function that uses the Python functionality and a public wrapper function that uses the call_python_func function. Here is the updated get function and its partner:

string ConfigParser::get(const string &attr, const string &section){
    return call_python_func<string>(boost::bind(&ConfigParser::get_py, this, attr, section),
                                    "Error getting configuration option: ");
}

string ConfigParser::get_py(const string &attr, const string &section){
    return py::extract<string>(conf_parser_.attr("get")(section, attr));
}

The get function binds the passed-in arguments, along with the implicit this pointer, to the get_py function, which in turn calls the boost::python functions necessary to perform the action. Simple and effective.

Of course, there is a tradeoff associated here. Instead of the repeated code of the try...catch blocks and Python error handling, there are double the number of functions declared per class. For my purposes, I prefer the second form, as it more effectively utilizes the compiler to find errors, but mileage may vary. The most important point is to handle Python errors at a level of code that understands Python. If your entire application needs to understand Python, you should consider rewriting in Python rather than embedding, perhaps with some C++ modules as needed.

As always, you can follow along with the tutorial by cloning the github repo.

Share

Comments

  1. Hello!

    I have enjoyed this series, thank you! Will there be a final part dealing with the issue of multiple threads? If so, I anxiously await it. This is the most important problem for me. You seem to imply that it is difficult or impossible. What about boost.python makes multi-threaded code hard? With what I need to do, I need to be able to call Python code from multiple threads.

    Thanks!

    Reply
    • Hi Brett, thanks for the comment, I’m glad you’re liking the series. There is more to come, including multithreading. Depending on what you mean by multithreading, it isn’t impossible or particularly hard (though it was hard for me to figure out the first time around), you just need to work around the Python GIL. Explicitly. But you can definitely call Python code from multiple threads. More on that to come.

      Reply
      • The only thing I’m really worried about is calling Python code from multiple threads. Even then, data can be thread-local and doesn’t need to be shared between threads. Reading about Python GIL, it seems it might be a barrier for me (but really I’m just waiting for you to explain it ;) . What I’ll be doing in these threads is HTTP communication, and each thread may take some time to execute fully, and I need them to all be working at the same time. Is that possible?

        Reply
        • Totally possible, the threads just can’t execute code simultaneously. In most cases, this isn’t an issue due to the way the Python interpreter does timeslicing. The only time it becomes an issue is if you e.g. want to double the processing power of your code by running on two cores. For an HTTP system (or any system with I/O and a reasonable amount of latency) it shouldn’t be an issue at all.

          Reply
          • When can I look forward to part 5 already? ;) This is the best tutorial I’ve found with regards to Python and C++. Rather follow your instructions than spend a few days diving in to boost.python docs.

  2. Hi Joseph!

    Thanks so much for this series, finding it was like hitting a gold mine for me. It got me further in half an hour than the official docs did in an entire day. I am getting 404 (not found) on the github link for the series’ source code, though — do you still have the sources?

    Again, thanks for writing this great tutorial — much appreciated!!

    Reply
  3. Hi,
    Thanks for a very useful tutorial! I’d be really interested if you have some experience or good tips for debugging workflows in this scenario. My world would be much simpler if I could step into the Python code inside my C++ debugger.

    Reply
    • Hi Petter, yes, debugging becomes a tremendous pain… my usual workflow was to write everything as a Python script and unit test it first, then use that (tested) code in C++. While hardly perfect, it at least allows you to be relatively confident that the problems that arise are in the binding rather than in the Python.

      Interestingly, since I wrote this tutorial I have moved all of my projects that use this paradigm to ‘compiled’ Python, using py2exe or cx-freeze. Though it required an initial porting challenge, working in pure Python has greatly improved the agility of these projects. I need to write a series on lessons learned through that process.

      Reply

Leave a Reply

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

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>