Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

boost::python Export Custom Exception

Tags:

I am currently writing a C++ extension for Python using Boost.Python. A function in this extension may generate an exception containing information about the error (beyond just a human-readable string describing what happened). I was hoping I could export this exception to Python so I could catch it and do something with the extra information.

For example:

import my_cpp_module try:     my_cpp_module.my_cpp_function() except my_cpp_module.MyCPPException, e:     print e.my_extra_data 

Unfortunately Boost.Python seems to translate all C++ exceptions (that are subclasses of std::exception) into RuntimeError. I realize that Boost.Python allows one to implement custom exception translation however, one needs to use PyErr_SetObject which takes a PyObject* (for the exception's type) and a PyObject* (for the exception's value)--neither of which I know how to get from my Boost.Python classes. Perhaps there is a way (which would be great) that I simply have not found yet. Otherwise does anyone know how to export a custom C++ exception so that I may catch it in Python?

like image 938
Jack Edmonds Avatar asked Feb 14 '10 16:02

Jack Edmonds


2 Answers

The solution is to create your exception class like any normal C++ class

class MyCPPException : public std::exception {...} 

The trick is that all boost::python::class_ instances hold a reference to the object's type which is accessible through their ptr() function. You can get this as you register the class with boost::python like so:

class_<MyCPPException> myCPPExceptionClass("MyCPPException"...); PyObject *myCPPExceptionType=myCPPExceptionClass.ptr(); register_exception_translator<MyCPPException>(&translateFunc); 

Finally, when you are translating the C++ exception to a Python exception, you do so as follows:

void translate(MyCPPException const &e) {     PyErr_SetObject(myCPPExceptionType, boost::python::object(e).ptr()); } 

Here is a full working example:

#include <boost/python.hpp> #include <assert.h> #include <iostream>  class MyCPPException : public std::exception { private:   std::string message;   std::string extraData; public:   MyCPPException(std::string message, std::string extraData)   {     this->message = message;     this->extraData = extraData;   }   const char *what() const throw()   {     return this->message.c_str();   }   ~MyCPPException() throw()   {   }   std::string getMessage()   {     return this->message;   }   std::string getExtraData()   {     return this->extraData;   } };  void my_cpp_function(bool throwException) {   std::cout << "Called a C++ function." << std::endl;   if (throwException)     {       throw MyCPPException("Throwing an exception as requested.",                "This is the extra data.");     } }  PyObject *myCPPExceptionType = NULL;  void translateMyCPPException(MyCPPException const &e) {   assert(myCPPExceptionType != NULL);   boost::python::object pythonExceptionInstance(e);   PyErr_SetObject(myCPPExceptionType, pythonExceptionInstance.ptr()); }  BOOST_PYTHON_MODULE(my_cpp_extension) {   boost::python::class_<MyCPPException>     myCPPExceptionClass("MyCPPException",             boost::python::init<std::string, std::string>());   myCPPExceptionClass.add_property("message", &MyCPPException::getMessage)     .add_property("extra_data", &MyCPPException::getExtraData);   myCPPExceptionType = myCPPExceptionClass.ptr();   boost::python::register_exception_translator<MyCPPException>     (&translateMyCPPException);   boost::python::def("my_cpp_function", &my_cpp_function); } 

Here is the Python code that calls the extension:

import my_cpp_extension try:     my_cpp_extension.my_cpp_function(False)     print 'This line should be reached as no exception should be thrown.' except my_cpp_extension.MyCPPException, e:     print 'Message:', e.message     print 'Extra data:',e.extra_data  try:     my_cpp_extension.my_cpp_function(True)     print ('This line should not be reached as an exception should have been' +        'thrown by now.') except my_cpp_extension.MyCPPException, e:     print 'Message:', e.message     print 'Extra data:',e.extra_data 
like image 84
Jack Edmonds Avatar answered Sep 22 '22 15:09

Jack Edmonds


The answer given by Jack Edmonds defines a Python "exception" class that does not inherit Exception (or any other built-in Python exception class). So although it can be caught with

except my_cpp_extension.MyCPPException as e:     ... 

it can not be caught with the usual catch all

except Exception as e:     ... 

Here is how to create a custom Python exception class that does inherit Exception.

like image 23
Johan Råde Avatar answered Sep 22 '22 15:09

Johan Råde