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.