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
Here's the solution from Jack Edmonds, ported to Python 3, using advice from here which itself uses code from here. Assembling it all together (and modernizing the C++ code a little bit) gives:
#include
#include
#include
class MyCPPException : public std::exception
{
public:
MyCPPException(const std::string &message, const std::string &extraData)
: message(message), extraData(extraData)
{
}
const char *what() const noexcept override
{
return message.c_str();
}
std::string getMessage() const
{
return message;
}
std::string getExtraData() const
{
return extraData;
}
private:
std::string message;
std::string 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.");
}
}
static PyObject* createExceptionClass(const char* name, PyObject* baseTypeObj = PyExc_Exception)
{
using std::string;
namespace bp = boost::python;
const string scopeName = bp::extract(bp::scope().attr("__name__"));
const string qualifiedName0 = scopeName + "." + name;
PyObject* typeObj = PyErr_NewException(qualifiedName0.c_str(), baseTypeObj, 0);
if (!typeObj) bp::throw_error_already_set();
bp::scope().attr(name) = bp::handle<>(bp::borrowed(typeObj));
return typeObj;
}
static PyObject *pythonExceptionType = NULL;
static void translateMyCPPException(MyCPPException const &e)
{
using namespace boost;
python::object exc_t(python::handle<>(python::borrowed(pythonExceptionType)));
exc_t.attr("cause") = python::object(e); // add the wrapped exception to the Python exception
exc_t.attr("what") = python::object(e.what()); // for convenience
PyErr_SetString(pythonExceptionType, e.what()); // the string is used by print(exception) in python
}
BOOST_PYTHON_MODULE(my_cpp_extension)
{
using namespace boost;
python::class_
myCPPExceptionClass("MyCPPException",
python::init());
myCPPExceptionClass.add_property("message", &MyCPPException::getMessage)
.add_property("extra_data", &MyCPPException::getExtraData);
pythonExceptionType = createExceptionClass("MyPythonException");
python::register_exception_translator(&translateMyCPPException);
python::def("my_cpp_function", &my_cpp_function);
}
and the python file to test it:
#!/usr/bin/env python3
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.MyPythonException as e:
print('Message:', e.what)
print('Extra data:',e.cause.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.MyPythonException as e:
print('Message:', e.what)
print('Extra data:',e.cause.extra_data)
And catching it as a standard python Exception works too:
except Exception as e:
print('Exception: ',e)