Differences between revisions 7 and 8
Revision 7 as of 2009-11-26 11:20:54
Size: 6652
Editor: PaulBoddie
Comment: Added syntax colouring, some line numbering.
Revision 8 as of 2009-11-26 21:55:27
Size: 7345
Editor: CPE001bfc8b793b-CM000a73a081a5
Comment:
Deletions are marked like this. Additions are marked like this.
Line 120: Line 120:

== Extracting Python exceptions ==

{{{#!cplusplus
myapp::String
myapp::python::currentException()
{
  using namespace boost::python;
  namespace py = boost::python;

  PyObject *exc,*val,*tb;
  PyErr_Fetch(&exc,&val,&tb);
  handle<> hexc(exc),hval(val),htb(tb);
  if(!htb || !hval)
  {
    MYAPP_ASSERT_BUG(hexc);
    return extract<myapp::String>(str(hexc));
  }
  else
  {
    object traceback(py::import("traceback"));
    object format_exception(traceback.attr("format_exception"));
    object formatted_list(format_exception(hexc,hval,htb));
    object formatted(str("\n").join(formatted_list));
    return extract<myapp::String>(formatted);
  }
}

}}}

Full example

While Boost.Python does not provide all the constructs necessary for embedding Python in a C++ application, using it will still dramatically help with the task.

Note: Because there is very little documentation on a lot of this, I am not entirely sure that the methods I am describing are the best to accomplish the task. If there is a better way, please write it up here.

First, a simple "Hello, World" script, run from a C++ program:

   1 #include <boost/python.hpp>
   2 
   3 using namespace boost::python;
   4 
   5 int main( int argc, char ** argv ) {
   6   try {
   7     Py_Initialize();
   8 
   9     object main_module((
  10       handle<>(borrowed(PyImport_AddModule("__main__")))));
  11 
  12     object main_namespace = main_module.attr("__dict__");
  13 
  14     handle<> ignored(( PyRun_String( "print \"Hello, World\"",
  15                                      Py_file_input,
  16                                      main_namespace.ptr(),
  17                                      main_namespace.ptr() ) ));
  18   } catch( error_already_set ) {
  19     PyErr_Print();
  20   }
  21 }

Compile this as a normal C++ program, linking it to libpython and libboost_python, and make sure that the compiler knows where the Python include files are. When run, it should print to the command line "Hello, World". Since this is all in the Boost.Python tutorial, I will skip the line-by-line explanation for now.

This is all well and good, but it is pretty useless, since you might as well have sent the Python code directly to the interpreter and saved yourself a lot of hassle. What you really want is to provide the Python code executed from the PyRun_* function with access to your C++ program's classes and data, so that the Python code can get some work done.

Before we can do that, let's define a simple C++ class that we want the Python code to access:

   1 class CppClass {
   2 public:
   3   int getNum() {
   4     return 7;
   5   }
   6 };

Now for the fun stuff, providing the embedded Python script with access to our new class. In our previous main() function, add after the object main_namespace is declared:

  13 main_namespace["CppClass"] = class_<CppClass>("CppClass")
  14                                .def("getNum",&CppClass::getNum);

Of course, if the CppClass were more complicated, it would be necessary to use a more complicated series of defs. Now you can replace the "print Hello, World" Python code with the following, to call the getNum(), and you should see "7" printed to the console:

"cpp = CppClass()\n"
"print cpp.getNum()\n"

So, now you can create C++ objects in Python, but you will probably want the Python code to be able to access existing C++ data. To do this, first create an instance of CppClass (we'll call ours cpp) at the beginning of main(). Now, after the class_ declaration, add:

   7 main_namespace["cpp"] = ptr(&cpp);

Now, you should be able to change your Python code to "print cpp.getNum()", and the number 7 should again be printed to the command line. The difference here is that the Python code and C++ code all share the same object. We passed a ptr into the Python interpreter to prevent a copy of the object from being made. We could have directly assigned cpp to the namespace, but any changes made to the object by the Python code would have been made to a local version of the object, not the instance which the C++ code has access to.

Now would also be a good time to point out that while Python manages memory for you, C++ does not. Always make sure that data available to the Python interpreter is actually available, or you will get mysterious crashes which may be hard to track down.

So, we finally have access to our C++ application's data, but now the script programmers are complaining that all of the data is in the global namespace, mucking up their scripts and generally getting in the way. To avoid this, and provide proper encapsulation, we should put all of our classes and data into a module. The first step is to remove all the lines which modify the main_namespace object, and then add a standard Boost module definition:

   1 BOOST_PYTHON_MODULE(CppMod) {
   2   class_<CppClass>("CppClass")
   3     .def("getNum",&CppClass::getNum);
   4 }

Now, inside main() and before the call to Py_Initialize() we want to call PyImport_AppendInittab( "CppMod", &initCppMod ); initCppMod is a function created by the BOOST_PYTHON_MODULE macro which is used to initialize the Python module CppMod. At this point, your embedded python script may call import CppMod and then access CppClass as a member of the module.

It would be convenient, however, if the module was already imported, so that the programmer does not have to manually load the module at the beginning of each script. To do this, add the following after the main_namespace object has been initialized:

object cpp_module( (handle<>(PyImport_ImportModule("CppMod"))) );
main_namespace["CppMod"] = cpp_module;

The first line loads the module, executing the initCppMod function, and the second line makes the loaded module available in the main namespace. Scripts may now refer to CppMod without needing to manually import it.

This also allows us to add data to the already-imported module. We can do this from within main() with the following line:

scope(cpp_module).attr("cpp") = ptr(&cpp);

Scripts are now able to access the cpp instance of CppClass from the CppMod module.

While this is certainly not complete, hopefully it will provide a starting point for using Boost.Python to embed Python scripts in your applications.

--Thomas Stephens <spiralman at gmail.com>

Tips and Tricks

Loading a module by full or relative path

The main benefit of this approach is that you don't need to worry about escaping strings.

   1 boost::python::object
   2 myapp::python::import( const myapp::Path & s )
   3 {
   4   using namespace boost::python;
   5   try
   6   {
   7     // If path is /Users/whatever/blah/foo.py
   8     dict locals;
   9     locals["modulename"] = s.filebase(); // foo -> module name
  10     locals["path"]   = s.get(); // /Users/whatever/blah/foo.py
  11     exec("import imp\n"
  12          "newmodule = imp.load_module(modulename,open(path),path,('py','U',imp.PY_SOURCE))\n",
  13          globals(),locals);
  14     return locals["newmodule"];
  15   }
  16   catch( error_already_set )
  17   {
  18     error(currentException());
  19     return object();
  20   }
  21 }

Extracting Python exceptions

   1 myapp::String
   2 myapp::python::currentException()
   3 {
   4   using namespace boost::python;
   5   namespace py = boost::python;
   6 
   7   PyObject *exc,*val,*tb;
   8   PyErr_Fetch(&exc,&val,&tb);
   9   handle<> hexc(exc),hval(val),htb(tb);
  10   if(!htb || !hval)
  11   {
  12     MYAPP_ASSERT_BUG(hexc);
  13     return extract<myapp::String>(str(hexc));
  14   }
  15   else
  16   {
  17     object traceback(py::import("traceback"));
  18     object format_exception(traceback.attr("format_exception"));
  19     object formatted_list(format_exception(hexc,hval,htb));    
  20     object formatted(str("\n").join(formatted_list));
  21     return extract<myapp::String>(formatted);
  22   }
  23 }

boost.python/EmbeddingPython (last edited 2011-07-14 04:38:21 by 2001:4978:27b:0:8d4c:271:fc15:ae08)

Unable to edit the page? See the FrontPage for instructions.