boost::python Export Custom Exception boost::python Export Custom Exception python python

boost::python Export Custom Exception


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_extensiontry:    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_datatry:    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


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.


Thanks to variadic templates and generalized lambda capture, we can collapse Jack Edmond's answer into something much more manageable and hide all of the cruft from the user:

template <class E, class... Policies, class... Args>py::class_<E, Policies...> exception_(Args&&... args) {    py::class_<E, Policies...> cls(std::forward<Args>(args)...);    py::register_exception_translator<E>([ptr=cls.ptr()](E const& e){        PyErr_SetObject(ptr, py::object(e).ptr());    });    return cls;}

To expose MyCPPException as an exception, you just need to change py::class_ in the bindings to exception_:

exception_<MyCPPException>("MyCPPException", py::init<std::string, std::string>())    .add_property("message", &MyCPPException::getMessage)    .add_property("extra_data", &MyCPPException::getExtraData);

And now we're back to the niceties of Boost.Python: don't need to name the class_ instance, don't need this extra PyObject*, and don't need an extra function somewhere.