Exceptions between C++ and Python
In this post, the tale of the friendship between C++ and Python will be the surprisingly little use of Boost Python. Exceptions to transfer back and forth is indeed the weak point of this library. Let’s go native Python API, and wherever possible use the Boost Python.
Boost nonetheless. Python creates an environment in which C++ exceptions fall into Python as a standard, and Runtime Error back from Python to C++ exception is thrown type error_already_set, which means “something you flew, go Sam read what is there. And here we will just do not use C-Python API, to deduct the necessary information about the exception and convert it into the appropriate class for logic applications.
What such complexity? -The fact of the matter is that in Python, unlike C++, in addition to the text of the exception and its type also comes a traceback — stack up to the location of the exception. Let’s extend the standard std: exception additional parameter for this report, as well as write exception converter back and forth from C++ classes in Python exception classes.
In the previous series
-
Boost Python. Introduction. C++ wrapper in Python.
-
Boost Python. Wrapper particular C++ classes.
-
Boost Python. Create type converters between C++ and Python.
Introduction
Suppose there is a particular hierarchy of exceptions, which you need to submit it unambiguously as the corresponding class in the C++ and Python when handling an exception. It’s especially true if you combine application logic in C++ and complex scripting in Python binding, or if you write a module for Python in C++ with complex logic. Sooner or later we set against the processing of exceptions coming from C++ to Python or vice versa.
Of course, in most cases, you will only need a standard mechanism to Boost. Python conversion exceptions from C++ to Python as a RuntimeException with text came from exception: what (). C++ side needs to catch an exception of type error_already_set and use the native Python API, but you can deduct only the type and not the exception text but is in fact history exception traceback.
But everything in order.
The journey of a small exception from C++ to Python
So you’ve written a module in C++ using Boost. Python hooked it in Python code through regular import and used one of these wrappers functions or methods. Valid in C++ code throws an exception through the most usual equally reasonable throw. In Python code, you will receive a RuntimeException with text derived from exception: what if this exception originated from STD:: exception.
If you are satisfied with what you except the exception text will get nothing, you can even do nothing more. If, however, you will need to catch an exception class defined errors strictly, it will need a little work.
Boost.Python provides the ability to register their broadcast exceptions leaving native penates module written in C++.All you need to do: call the module declaration in a function template function boost:p python: register_exception_translator<T, F>(F), where T is the type of C++ exceptions. F is a function that takes a reference to an exception of this kind andsomehow performing its duty to transfer the desired type of derogations from the external code is in Python. In general terms, like so:
void translate_error (const error &);
…
BOOST_PYTHON_MODULE (…)
{
…
register_exception_translator(translate_error);
}
…
void translate_error (const error & e)
{
PyErr_SetString (PyExc_Exception, e. what ());
}
Here, we used a standard exception type Exception built into Python, but you can use absolutely any exception: standard external connected via import obtaining PyObject * through an object:: p, tr () or even your own created right here on the spot, via PyErr_NewException.
Let’s add a couple of completeness of sensations classes which will be given to both analog and ValueError ZeroDivisionError and full of happiness will inherit them from our error, we’ll call them accordingly and zero_division_error value_error:
class error: public Exception
{
public:
error ();
error (string const & message);
error (string message, string & const const & details);
virtual const char * what() const;
virtual const string & get_message () const;
virtual const string & get_details () const;
virtual const char * type () const;
private:
string m_message;
string m_details;
};
class value_error: public error
{
public:
value_error ();
value_error (string const & message);
value_error (string message, string & const const & details);
virtual const char * type () const;
};
class zero_division_error: public error
{
public:
zero_division_error ();
zero_division_error (string const & message);
zero_division_error (string message, string & const const & details);
virtual const char * type () const;
};
We need a m_details box on the way back from Python to C++, to preserve the traceback for example. And the method type () needs for debugging later. Straightforward and clear hierarchy.
Register for our translators function exceptions in Python:
void translate_error (const error &);
void translate_value_error (const value_error &);
void translate_zero_division_error (const zero_division_error &);
…
BOOST_PYTHON_MODULE (…)
{
…
register_exception_translator<error>(translate_error);
register_exception_translator<value_error>(translate_value_error);
register_exception_translator<zero_division_error>(translate_zero_division_error);
}
…
void translate_error (const error & e)
{
PyErr_SetString (PyExc_Exception, e. what ());
}
void translate_value_error (const & value_error e)
{
PyErr_SetString (PyExc_ValueError, e. what ());
}
void translate_zero_division_error (const & zero_division_error e)
{
PyErr_SetString (PyExc_ZeroDivisionError, e. what ());
}
</zero_division_error></value_error></error>Excellent, we only make the C++ side test functions that can throw these exceptions:
double divide (double a, double b)
{
If (abs (b) <> </> <double>: epsilon ())
throw zero_division_error ();
return a/b;
}
Double to_num (const char * val)
{
Double res;
If (val! |!</double>sscanf (val, “% LG” & res))
throw value_error ();
return res;
}
void test (bool Val)
{
If (! Val)
throw error (“Test failure”, “test”);
}
Why not, these functions are not worse than any other and throw what we need.
Wrap them:
…
BOOST_PYTHON_MODULE (python_module)
{
register_exception_translator<error>(translate_error);
</error>register_exception_translator<value_error>(translate_value_error);
register_exception_translator<zero_division_error>(translate_zero_division_error);
def (“divide”, divide, args (“a”, “b”));
def (“to_num”, to_num, args (“val”));
def (“test”, “test, args (” val “));
}
…
</zero_division_error></value_error>Well, collect our module, perform import python_module, call our function with the desired parameters, get necessary exceptions (Python script 3. x):
import python_module as pm
try:
try:
RES = pm. divide (1, 0)
except ZeroDivisionError:
print (“ZeroDivisionError-OK”)
except Exception as e:
print (“Expected ZeroDivisionError, but an exception of type ‘ {t} ‘ with text: ‘ {e} ‘.” format (t = type (e) e = (e)))
else statement:
print (“Expected ZeroDivisionError, but no exception raised! Result: {r}. “format (r = res))
try:
RES = pm. to_num (‘ qwe ‘)
except ValueError:
print (“ValueError-OK”)
except Exception as e:
print (“ValueError exception, but Expected of type ‘ {t} ‘ with text: ‘ {e} ‘.” format (t = type (e) e = (e)))
else statement:
print (“Expected ValueError, but no exception raised! Result: {r}. “format (r = res))
try:
RES = pm. test (False)
except Exception as e:
If type (e) is an Exception:
print (“Exception-OK”)
else statement:
print (“Exception of type ‘ {t} ‘ expected type ‘ Exception ‘, message: ‘ {e} ‘.” format (t = type (e) e = (e)))
else statement:
print (“Expected Exception, but no exception raised! Result: {r}. “format (r = res))
Script output:
-
ZeroDivisionError Is OK
-
ValueError Is OK
-
Exception — OK
The adventures of our exclusion from Python to C++
Let’s select in a separate project exception types and test functions and will collect from them a distinct dynamic-link library error_types. Python module will receive separately in draft python_module.
And now let’s C++ application where we will catch exceptions from Python, let’s call it catch_exceptions.
All you need is to connect our module via import (“python_module»), then get access to module functions through the attr (” divide “) attr (” to_num “), attr (” test “). We will call them; they will generate C++ code level exclusions, will be held in the Python interpreter further into the C++ application by calling exception error_already_set Boost library exception.Python-penned just for such cases.
Itself an object of type error_already_set, it is important just to catch an exception. In General, the handling of such an exception is as follows:
catch (const error_already_set &)
{
PyObject * exc * val * tb;
PyErr_Fetch (& exc, & val & tb);
PyErr_NormalizeException (& exc, & val & tb);
handle<> </> hexc (exc), hval (allow_null (val)), htb (allow_null (tb));
throw error (extract<string>(! hval? str (hexc): str (hval)));
}
So we get an exception always of the same type, but at least we will be able to retrieve the text of the exception.</string> But we come to the exception type in the variable etc., himself an exception object in the variable var and even object with a stack exception that occurred in a variable too. Let’s convert the exception that took place in the zero_division_error and value_error if it’s ZeroDivisionError or ValueError respectively.
Stop! Not everything is clear to everyone that these two functions, why all the PyObject * where exceptions in C-API if they don’t exist in c, let us read more.
Yes, in pure Si no exceptions, but they are in Python, and its API provides the ability to pull information about the exception. Python, C API all values and types, generally almost everything is represented as PyObject *, so the exception E-type T is a pair of type PyObject *, add to this another PyObject * for traceback — saved stack where the exception occurred.
Pull information about the exception can be a function PyErr_Fetch, then information about the exception you can normalize (if you do not want to work in the internal representation in the tuple form) PyErr_NormalizeException function.
After calling couples these features we fill three values of type PyObject * respectively: exception class instance (object) of the exception and stack (traceback) saved at the moment of throwing an exception.
Further, where it is more convenient to work with Boost Python wrap PyObject * boost:: p then: handle, compatible < > with any object libraries Boost Python, we just need a boost:: p then: str. After conversion to analog lines of Python in Boost. Python we can pull the standard native string to STDs:: string C++ language. If you prefer, you can pick and the usual cast chair *.
With the first two parameters, everything is clear, they converted to a string, but still have to convert to traceback. The easiest way to do this using a traceback, passing our three format_exception parameters to a function. Trackback function. format_exception (ex, Val, TB) return to us an array of strings in the form of a standard list of Python, which remarkably will be in one big thick line.
On the side of C++, using the Boost. Python, it will look like this:
…
format_exception = import (‘ traceback ‘). attr (‘ format_exception “);
return extract<string>(str (“.”) join (format_exception (exc, val, tb)));
}
You can make a helper function to generate rows from exception.</string> The problem, however, is that calling import () to work each time will lead to a high call, so the object derived from import (“trackback”).Attr (“format_exception) best save the result of the function in a separate object, also we need to store the result of the import (” python_module “). Given that it takes to make somewhere between Py_Initialize () and Py_Finalize (), there is nothing better to hold such a singleton field variable in the head does come.
Working with Python APIs via singleton
So let’s Let’s Singleton, it would complicate the application, but few will simplify the code and will initialize correctly work with the interpreter, keep all supporting objects and complete everything properly:
class python_interpreter
{
public:
static double divide (double, double);
static double to_num (const string &);
static void test (bool);
static string format_error (handle < > const &, handle < > const &, handle < > const &);
private:
object m_python_module;
object m_format_exception;
python_interpreter ();
~ python_interpreter ();
static python_interpreter & instance ();
object & python_module ();
string format_error (object const &, object const &, object const &);
};
The constructor will initialize the work with the interpreter and the destructor roughen up the saved fields and de-initialize interpreter work methods and import format_error python_module COOtvetstvujushhie modules only once:
python_interpreter::p ython_interpreter ()
{
Py_Initialize ();
}
python_interpreter: ~ python_interpreter ()
{
m_python_module = object ();
m_format_exception = object ();
Py_Finalize ();
}
Double python_interpreter: ivide:d (double a, double b)
{
return extract<double>(instance (). python_module (). attr (“divide”) (a, b));
}
Double python_interpreter: to_num (string const & val)
{
return extract<double>(instance (). python_module (). attr (“to_num”) (val));
}
void python_interpreter: test (bool val)
{
</double></double> instance (). python_module (). attr (‘ test ‘) (val);
}
string python_interpreter: format_error (handle < > const & exc, handle < > const & msg handle < > const & tb)
{
return instance (). format_error (object (exc), object (msg), object (tb));
}
python_interpreter & python_interpreter: instance ()
{
static python_interpreter single;
return single;
}
object & python_interpreter::p ython_module ()
{
If (m_python_module. is_none)
m_python_module = import (“python_module”);
return m_python_module;
}
string python_interpreter: format_error (object const & exc, object const & val, object const & tb)
{
If (m_format_exception.is_none ())
m_format_exception = import (‘ traceback ‘). attr (‘ format_exception “);
return extract<string>(str (“.”) join (m_format_exception (exc, val, tb)));
}
</string>Total we got ready mechanism applicable to any application in C++, using Python as a powerful helper functionality with a bunch of libraries.
It’s time to check out our exception mechanism!
Check the exception translation mechanism of Python in C++
Let’s support function:
void rethrow_python_exception ()
{
PyObject * exc * val * tb;
PyErr_Fetch (& exc, & val & tb);
PyErr_NormalizeException (& exc, & val & tb);
handle<> </> hexc (exc), hval (allow_null (val)), htb (allow_null (tb));
string message, details;
message = extract<string>(! hval? str (hexc): str (hval));
details =! tb? extract<string>(str (hexc)): python_interpreter: format_error (hexc, hval, htb);
If (PyObject_IsSubclass (exc, PyExc_ZeroDivisionError))
throw zero_division_error (message, details);
else if (PyObject_IsSubclass (exc, PyExc_ValueError))
throw value_error (message, details);
else statement
throw error (message, details);
}
</string></string>Then exception handling mechanism to the following diagram for each test method as divide:
try
{
try
{
ivide:d: python_interpreter (1, 0);
}
catch (const error_already_set &)
{
rethrow_python_exception ();
}
}
catch (const error & e)
{
output_error (e);
}
Here output_error the simplest function that displays information about an exception like this:
void output_error (const error & e)
{
cerr < “nError: type < < < e. type() < nMessage:” “< < < e.get _message ()”: nDetails < < < < e.get _details () < < endl;
}
Here we found a virtual method type() which we have brought in a base class.
Create such a partition for the to_num and the test and check that will come if you only run Python string “1/0” through exec:
try
{
try
{
exec (“1/0”);
}
catch (const error_already_set &)
{
rethrow_python_exception ();
}
}
catch (const error & e)
{
output_error (e);
}
Run …
The output should be similar to the following:
Error type: zero_division_error
Message: Division by zero!
Details: <class ‘zerodivisionerror’=””>
Error type: value_error
Message: Inappropriate value!
Details: <class ‘valueerror’=””>
Error type: error
Message: Test failure.
Details: <class ‘exception’=””>
Error type: zero_division_error
</class></class></class>Message: division by zero
Details: Traceback (most recent call last):
File “”, line 1, in ZeroDivisionError: division by zero