c++ - python3 - python raise exception
boost:: Python Export Custom Exception (3)
Actualmente estoy escribiendo una extensión de C ++ para Python usando Boost.Python. Una función en esta extensión puede generar una excepción que contiene información sobre el error (más allá de una cadena legible por el hombre que describe lo que sucedió). Esperaba poder exportar esta excepción a Python para poder capturarla y hacer algo con la información adicional.
Por ejemplo:
import my_cpp_module
try:
my_cpp_module.my_cpp_function()
except my_cpp_module.MyCPPException, e:
print e.my_extra_data
Desafortunadamente, Boost.Python parece traducir todas las excepciones de C ++ (que son subclases de std::exception
) a RuntimeError
. Me doy cuenta de que Boost.Python permite implementar una traducción personalizada de excepciones, sin embargo, se necesita usar PyErr_SetObject
que toma un PyObject*
(para el tipo de excepción) y un PyObject*
(para el valor de la excepción), ninguno de los cuales sé cómo Obtener de mis clases Boost.Python. Quizás haya una manera (que sería genial) que simplemente no he encontrado todavía. De lo contrario, ¿alguien sabe cómo exportar una excepción de C ++ personalizada para poder capturarla en Python?
Gracias a las plantillas variadas y la captura lambda generalizada, podemos colapsar la respuesta de Jack Edmond en algo mucho más manejable y ocultar todo el contenido del usuario:
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;
}
Para exponer MyCPPException
como una excepción, solo necesita cambiar py::class_
en los enlaces a exception_
:
exception_<MyCPPException>("MyCPPException", py::init<std::string, std::string>())
.add_property("message", &MyCPPException::getMessage)
.add_property("extra_data", &MyCPPException::getExtraData)
;
Y ahora volvemos a las sutilezas de Boost.Python: no necesitamos nombrar a la instancia class_
, no necesitamos este PyObject*
adicional, y no necesitamos una función adicional en algún lugar.
La respuesta dada por Jack Edmonds define una clase de "excepción" de Python que no hereda la Exception
(o cualquier otra clase de excepción de Python integrada). Así que aunque puede ser atrapado con
except my_cpp_extension.MyCPPException as e:
...
No se puede atrapar con la captura habitual de todos.
except Exception as e:
...
Here se explica cómo crear una clase de excepción Python personalizada que herede la Exception
.
La solución es crear su clase de excepción como cualquier clase normal de C ++.
class MyCPPException : public std::exception {...}
El truco es que todas las instancias boost :: python :: class_ contienen una referencia al tipo de objeto que es accesible a través de su función ptr (). Puede obtener esto cuando registre la clase con boost :: python así:
class_<MyCPPException> myCPPExceptionClass("MyCPPException"...);
PyObject *myCPPExceptionType=myCPPExceptionClass.ptr();
register_exception_translator<MyCPPException>(&translateFunc);
Finalmente, cuando está traduciendo la excepción de C ++ a una excepción de Python, haga lo siguiente:
void translate(MyCPPException const &e)
{
PyErr_SetObject(myCPPExceptionType, boost::python::object(e).ptr());
}
Aquí hay un ejemplo completo de trabajo:
#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);
}
Aquí está el código de Python que llama a la extensión:
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