PrevUpHomeNext

Embedding

Using the interpreter

By now you should know how to use Boost.Python to call your C++ code from Python. However, sometimes you may need to do the reverse: call Python code from the C++-side. This requires you to embed the Python interpreter into your C++ program.

Currently, Boost.Python does not directly support everything you'll need when embedding. Therefore you'll need to use the Python/C API to fill in the gaps. However, Boost.Python already makes embedding a lot easier and, in a future version, it may become unnecessary to touch the Python/C API at all. So stay tuned...

Building embedded programs

To be able to embed python into your programs, you have to link to both Boost.Python's as well as Python's own runtime library.

Boost.Python's library comes in two variants. Both are located in Boost's /libs/python/build/bin-stage subdirectory. On Windows, the variants are called boost_python.lib (for release builds) and boost_python_debug.lib (for debugging). If you can't find the libraries, you probably haven't built Boost.Python yet. See Building and Testing on how to do this.

Python's library can be found in the /libs subdirectory of your Python directory. On Windows it is called pythonXY.lib where X.Y is your major Python version number.

Additionally, Python's /include subdirectory has to be added to your include path.

In a Jamfile, all the above boils down to:

projectroot c:\projects\embedded_program ; # location of the program

# bring in the rules for python
SEARCH on python.jam = $(BOOST_BUILD_PATH) ;
include python.jam ;

exe embedded_program # name of the executable
  : #sources
     embedded_program.cpp
  : # requirements
     <find-library>boost_python <library-path>c:\boost\libs\python
  $(PYTHON_PROPERTIES)
    <library-path>$(PYTHON_LIB_PATH)
    <find-library>$(PYTHON_EMBEDDED_LIBRARY) ;

Getting started

Being able to build is nice, but there is nothing to build yet. Embedding the Python interpreter into one of your C++ programs requires these 4 steps:

  1. #include <boost/python.hpp>
  2. Call Py_Initialize() to start the interpreter and create the __main__ module.
  3. Call other Python C API routines to use the interpreter.
[Note] Note

Note that at this time you must not call Py_Finalize() to stop the interpreter. This may be fixed in a future version of boost.python.

(Of course, there can be other C++ code between all of these steps.)

Now that we can embed the interpreter in our programs, lets see how to put it to use...

As you probably already know, objects in Python are reference-counted. Naturally, the PyObjects of the Python C API are also reference-counted. There is a difference however. While the reference-counting is fully automatic in Python, the Python C API requires you to do it by hand. This is messy and especially hard to get right in the presence of C++ exceptions. Fortunately Boost.Python provides the handle and object class templates to automate the process.

Running Python code

Boost.python provides three related functions to run Python code from C++.

object eval(str expression, object globals = object(), object locals = object())
object exec(str code, object globals = object(), object locals = object())
object exec_file(str filename, object globals = object(), object locals = object())

eval evaluates the given expression and returns the resulting value. exec executes the given code (typically a set of statements) returning the result, and exec_file executes the code contained in the given file.

There are also overloads taking char const* instead of str as the first argument.

The globals and locals parameters are Python dictionaries containing the globals and locals of the context in which to run the code. For most intents and purposes you can use the namespace dictionary of the __main__ module for both parameters.

Boost.python provides a function to import a module:

object import(str name)

import imports a python module (potentially loading it into the running process first), and returns it.

Let's import the __main__ module and run some Python code in its namespace:

object main_module = import("__main__");
object main_namespace = main_module.attr("__dict__");

object ignored = exec("hello = file('hello.txt', 'w')\n"
                      "hello.write('Hello world!')\n"
                      "hello.close()",
                      main_namespace);

This should create a file called 'hello.txt' in the current directory containing a phrase that is well-known in programming circles.

Manipulating Python objects

Often we'd like to have a class to manipulate Python objects. But we have already seen such a class above, and in the previous section: the aptly named object class and its derivatives. We've already seen that they can be constructed from a handle. The following examples should further illustrate this fact:

object main_module = import("__main__");
object main_namespace = main_module.attr("__dict__");
object ignored = exec("result = 5 ** 2", main_namespace);
int five_squared = extract<int>(main_namespace["result"]);

Here we create a dictionary object for the __main__ module's namespace. Then we assign 5 squared to the result variable and read this variable from the dictionary. Another way to achieve the same result is to use eval instead, which returns the result directly:

object result = eval("5 ** 2");
int five_squared = extract<int>(result);

Exception handling

If an exception occurs in the evaluation of the python expression, error_already_set is thrown:

try
{
    object result = eval("5/0");
    // execution will never get here:
    int five_divided_by_zero = extract<int>(result);
}
catch(error_already_set const &)
{
    // handle the exception in some way
}

The error_already_set exception class doesn't carry any information in itself. To find out more about the Python exception that occurred, you need to use the exception handling functions of the Python C API in your catch-statement. This can be as simple as calling PyErr_Print() to print the exception's traceback to the console, or comparing the type of the exception with those of the standard exceptions:

catch(error_already_set const &)
{
    if (PyErr_ExceptionMatches(PyExc_ZeroDivisionError))
    {
        // handle ZeroDivisionError specially
    }
    else
    {
        // print all other errors to stderr
        PyErr_Print();
    }
}

(To retrieve even more information from the exception you can use some of the other exception handling functions listed here.)


PrevUpHomeNext