[[SIGVerse with Python]] The aim of this tutorial is to show how to use exception handling to understand errors that occur in the Python code that boost::python handles. We use Python C API to read the information about the error, because the error message shown by boost::python itself is not so helpful. It will become more clear by the using the following example, in which shall import a fake python module called as "some_module" (it does not exist, we just want to see the information related with error message). The function for exception handling was taken from the [[blog:http://thejosephturner.com/blog/page/1]] written by Joseph Turner. Please use the following code and name it as "ControllerSample.cpp". #highlight(c){{ #include "ControllerEvent.h" #include "Controller.h" #include "Logger.h" #include <iostream> #include <boost/python.hpp> #include <Python.h> #include <dlfcn.h> namespace py = boost::python; // create namespace variable for boost::python using namespace std; class MyController : public Controller { public: void onInit(InitEvent &evt); double onAction(ActionEvent&); void onRecvMsg(RecvMsgEvent &evt); void onCollision(CollisionEvent &evt); }; void MyController::onInit(InitEvent &evt) { SimObj *my = getObj(myname()); dlopen("libpython2.7.so", RTLD_LAZY | RTLD_GLOBAL); Py_Initialize(); //initialization of the python interpreter } double MyController::onAction(ActionEvent &evt) { double x=2.0; Py_Initialize(); py::object main_module = py::import("__main__"); // load the dictionary object out of the main module py::object main_namespace = main_module.attr("__dict__"); // run simple code within the main namespace using the boost::python::exec // importing a fake module py::exec("import some_module", main_namespace); return 1.0; } void MyController::onRecvMsg(RecvMsgEvent &evt) { } void MyController::onCollision(CollisionEvent &evt) { } extern "C" Controller * createController() { return new MyController; } }} Please set the following environment variables to set the path to boost library. export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$HOME/pool/lib Now, execute "make" it will create a ControllerSample.so file. make Please copy the following xml file and name it as "WorldSample.xml" #highlight(xml){{ <?xml version="1.0" encoding="utf8"?> <world name="myworld1"> <gravity x="0.0" y="-980.7" z="0.0"/> <instanciate class="seToy_D.xml"> <set-attr-value name="name" value="toy_D"/> <set-attr-value name="language" value="c++"/> <set-attr-value name="implementation" value="./ControllerSample.so"/> <set-attr-value name="dynamics" value="true"/> <set-attr-value name="x" value="0.0"/> <set-attr-value name="y" value="60.0"/> <set-attr-value name="z" value="0.0"/> <set-attr-value name="mass" value="1.0"/> <set-attr-value name="collision" value="true"/> </instanciate> </world> }} Please copy the following Makefile file and name it as "Makefile". We shall be using the same Makefile in subsequent examples. #highlight(c){{ #sigverse header SIG_SRC = $(SIGVERSE_PATH)/include/sigverse export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/raghav/pool/lib CC = gcc CPP = g++ AS = as LD = g++ AR = ar RANLIB = ranlib OBJCOPY = objcopy # external libraries and headers # change the next line to point to the location of your boost installation EXTERN_DIR = /home/raghav/pool EXTERN_LIBDIR = $(EXTERN_DIR)/lib EXTERN_INCDIR = $(EXTERN_DIR)/include EXTERN_BINDIR = $(EXTERN_DIR)/bin BOOST_PYTHON_LIB = $(EXTERN_LIBDIR)/libboost_python.a INCDIRS = . INCDIRS += $(EXTERN_INCDIR) # you may also need to change this directory if you want to pin to a specific # python version INCDIRS += /usr/include/python2.7 INCDIRS += /usr/lib/python2.7/dist-packages/numpy/core/include INCDIR = $(foreach dir, $(INCDIRS), -I$(dir)) %.o: %.cpp $(CPP) $(INCDIR) $(CFLAGS) $(CPPFLAGS) -c $< -o $@ #specifying of object file OBJS = ControllerSample.so ControllerSample.so: ControllerSample.cpp g++ -DCONTROLLER -DNDEBUG -DUSE_ODE -DdDOUBLE -I$(SIG_SRC) -I$(SIG_SRC)/comm/controller -I$(INCDIR) -fPIC -shared -o $@ $^ $(LDFLAGS) -L$(EXTERN_LIBDIR) -lboost_python -lpython2.7 #$^ , $+ The names of all the prerequisites, with spaces between them. The value of $^ omits duplicate prerequisites, # while $+ retains them and preserves their order clean: rm -rf *.so *.o $(HWOBJS) hello_world }} Also set the PYTHONPATH export PYTHONPATH=$PYTHONPATH:/path_to_your_workspace Now, please load the world file into Sigverse and push "Start" Button. ./sigverse.sh -w ./WorldSample -p write_your_port_mumber It will output the following error …which is not-so useful terminate called after throwing an instance of 'boost::python::error_already_set' In short, any errors that occur in the Python code that boost::python handles will cause the library to raise this exception; unfortunately, the exception does not encapsulate any of the information about the error itself. To extract information about the error, we’re going to have to resort to using the Python C API and some Python itself. First, catch the error: #highlight(c){{ try{ py::object main_module = py::import("__main__"); // load the dictionary object out of the main module py::object main_namespace = main_module.attr("__dict__"); // run simple code within the main namespace using the boost::python::exec // of course, you can also import functionality from the standard library py::exec("import some_module", main_namespace); } catch(boost::python::error_already_set const &){ // Parse and output the exception std::string perror_str = parse_python_exception(); std::cout << "Error in Python: " << perror_str << std::endl; } }} We use the parse_python_exception() which is defined below: #highlight(c){{ std::string parse_python_exception(){ PyObject *type_ptr = NULL, *value_ptr = NULL, *traceback_ptr = NULL; // Fetch the exception info from the Python C API PyErr_Fetch(&type_ptr, &value_ptr, &traceback_ptr); // Fallback error std::string ret("Unfetchable Python error"); // If the fetch got a type pointer, parse the type into the exception string if(type_ptr != NULL){ py::handle<> h_type(type_ptr); py::str type_pstr(h_type); // Extract the string from the boost::python object py::extract<std::string> e_type_pstr(type_pstr); // If a valid string extraction is available, use it // otherwise use fallback if(e_type_pstr.check()) ret = e_type_pstr(); else ret = "Unknown exception type"; } // Do the same for the exception value (the stringification of the exception) if(value_ptr != NULL){ py::handle<> h_val(value_ptr); py::str a(h_val); py::extract<std::string> returned(a); if(returned.check()) ret += ": " + returned(); else ret += std::string(": Unparseable Python error: "); } // Parse lines from the traceback using the Python traceback module if(traceback_ptr != NULL){ py::handle<> h_tb(traceback_ptr); // Load the traceback module and the format_tb function py::object tb(py::import("traceback")); py::object fmt_tb(tb.attr("format_tb")); // Call format_tb to get a list of traceback strings py::object tb_list(fmt_tb(h_tb)); // Join the traceback strings into a single string py::object tb_str(py::str("\n").join(tb_list)); // Extract the string, check the extraction, and fallback in necessary py::extract<std::string> returned(tb_str); if(returned.check()) ret += ": " + returned(); else ret += std::string(": Unparseable Python traceback"); } return ret; } }} Please add the code snippet shown above and make the executable again. The complete code is given below for quick copy. #highlight(c){{ #include "ControllerEvent.h" #include "Controller.h" #include "Logger.h" #include <iostream> #include <boost/python.hpp> #include <Python.h> #include <dlfcn.h> namespace py = boost::python; // create namespace variable for boost::python using namespace std; std::string parse_python_exception(); // functional declaration for exception handling class MyController : public Controller { public: void onInit(InitEvent &evt); double onAction(ActionEvent&); void onRecvMsg(RecvMsgEvent &evt); void onCollision(CollisionEvent &evt); }; void MyController::onInit(InitEvent &evt) { SimObj *my = getObj(myname()); dlopen("libpython2.7.so", RTLD_LAZY | RTLD_GLOBAL); Py_Initialize(); //initialization of the python interpreter } double MyController::onAction(ActionEvent &evt) { double x=2.0; Py_Initialize(); try{ py::object main_module = py::import("__main__"); // load the dictionary object out of the main module py::object main_namespace = main_module.attr("__dict__"); // run simple code within the main namespace using the boost::python::exec // of course, you can also import functionality from the standard library py::exec("import some_module", main_namespace); } catch(boost::python::error_already_set const &){ // Parse and output the exception std::string perror_str = parse_python_exception(); std::cout << "Error in Python: " << perror_str << std::endl; } return 1.0; } void MyController::onRecvMsg(RecvMsgEvent &evt) { } void MyController::onCollision(CollisionEvent &evt) { } extern "C" Controller * createController() { return new MyController; } std::string parse_python_exception(){ PyObject *type_ptr = NULL, *value_ptr = NULL, *traceback_ptr = NULL; // Fetch the exception info from the Python C API PyErr_Fetch(&type_ptr, &value_ptr, &traceback_ptr); // Fallback error std::string ret("Unfetchable Python error"); // If the fetch got a type pointer, parse the type into the exception string if(type_ptr != NULL){ py::handle<> h_type(type_ptr); py::str type_pstr(h_type); // Extract the string from the boost::python object py::extract<std::string> e_type_pstr(type_pstr); // If a valid string extraction is available, use it // otherwise use fallback if(e_type_pstr.check()) ret = e_type_pstr(); else ret = "Unknown exception type"; } // Do the same for the exception value (the stringification of the exception) if(value_ptr != NULL){ py::handle<> h_val(value_ptr); py::str a(h_val); py::extract<std::string> returned(a); if(returned.check()) ret += ": " + returned(); else ret += std::string(": Unparseable Python error: "); } // Parse lines from the traceback using the Python traceback module if(traceback_ptr != NULL){ py::handle<> h_tb(traceback_ptr); // Load the traceback module and the format_tb function py::object tb(py::import("traceback")); py::object fmt_tb(tb.attr("format_tb")); // Call format_tb to get a list of traceback strings py::object tb_list(fmt_tb(h_tb)); // Join the traceback strings into a single string py::object tb_str(py::str("\n").join(tb_list)); // Extract the string, check the extraction, and fallback in necessary py::extract<std::string> returned(tb_str); if(returned.check()) ret += ": " + returned(); else ret += std::string(": Unparseable Python traceback"); } return ret; } }} Please execute on command line $make $sigserver.sh -w ./WorldSample.xml -p write_your_port_number The following information about the error is shown now. Error in Python: <type 'exceptions.ImportError'>: No module named some_module: File "<string>", line 1, in <module> As you can see, we can understand from this error that it is related with importing a module, which is certainly more useful from previous error message. From now, on we shall always use this exception handling in our code. #highlight(end)