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?
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
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.
来源:https://stackoverflow.com/questions/2261858/boostpython-export-custom-exception