[[SIGVerse with Python]]

We saw in the previous tutorial that the "exec" function runs the arbitrary code in the string parameter within the specified namespace. We showed the following examples for that, in which we showed the use of normal, non-imported code.
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. 

#highlight(python){{
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).

py::exec("print 'Hello, world'", main_namespace);  
py::exec("print 'Hello, world'[3:5]", main_namespace);  
py::exec("print '.'.join(['1','2','3'])", main_namespace);  
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.

Of course, we would like to import modules (both user defined and pre-designed system modules) and extract values for them. 
          export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$HOME/pool/lib

Here, I will show one example of standard module "random". This tutorial is designed from taking some examples from the blog by [[Joseph Turner: http://thejosephturner.com/blog/page/1]]
Now, execute "make" it will create a ControllerSample.so file.

#highlight(python){{
         make
 

   boost::python::exec("import random", main_namespace);
   boost::python::object rand = boost::python::eval("random.random()",      main_namespace);
    std::cout << py::extract<double>(rand) << std::endl
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>
}}

Here we imported the random module by executing the corresponding Python statement within the __main__ namespace, bringing the module into the namespace. After the module is available, we can use functions, objects, and variables within the namespace. In this example, we use the eval function, which returns the result of the passed-in Python statement, to create a boost::python object containing a random value as returned by the random() function in the random module. Finally, we extract the value as a C++ double type and print it.
Please copy the following Makefile file and name it as "Makefile". We shall be using the same Makefile in subsequent examples.


As suggested in the blog by, [[Joseph Turner: http://thejosephturner.com/blog/page/1]], there is a more object oriented way to do this:
#highlight(c){{

#highlight(python){{
#sigverse header
SIG_SRC  = $(SIGVERSE_PATH)/include/sigverse
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/raghav/pool/lib

    boost::python::object rand_mod = boost::python::import("random");
    boost::python::object rand_func = rand_mod.attr("random");
    boost::python::object rand2 = rand_func();
    std::cout << boost::python::extract(rand2) << std::endl;
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



}}


In this final example, we import the random module, but this time using the boost::python import function, which loads the module into a boost Python object. Next, the random function object is extracted from the random module and stored in a boost::python object. The function is called, returning a Python object containing the random number. Finally, the double value is extracted and printed. In general, all Python objects can be handled in this way – functions, classes, built-in types

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)



Front page   New List of pages Search Recent changes   Help   RSS of recent changes