...one of the most highly
regarded and expertly designed C++ library projects in the
world.
— Herb Sutter and Andrei
Alexandrescu, C++
Coding Standards
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...
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) ;
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:
<boost/python.hpp>
__main__
module.
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 PyObject
s 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.
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.
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.
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);
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.)