- The added line is THIS COLOR.
- The deleted line is THIS COLOR.
[[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)
#counter