2010-02-14 11 views
22

Actualmente estoy escribiendo una extensión C++ para Python usando Boost.Python. Una función en esta extensión puede generar una excepción que contenga información sobre el error (más allá de una cadena legible por humanos que describa lo sucedido). Esperaba poder exportar esta excepción a Python para poder atraparla y hacer algo con la información adicional.boost :: python Exportar Excepción personalizada

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 traducirse excepciones todas C++ (que son subclases de std::exception) en RuntimeError. Me doy cuenta de que Boost.Python permite implementar traducciones de excepciones personalizadas, sin embargo, uno necesita usar PyErr_SetObject que toma PyObject* (para el tipo de la excepción) y PyObject* (por el valor de la excepción), ninguno de los cuales sé cómo obtener de mis clases de Boost.Python. Tal vez 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 C++ personalizada para que pueda atraparlo en Python?

+1

** ¡Buena pregunta y respuesta! ** ¡Me salvó la vida! Gracias. –

+0

¡Excelente! muy útil aquí también! Me gustaría 5x votar si pudiera :) –

Respuesta

25

La solución es crear su clase de excepción como cualquier normal C++ clase

class MyCPPException : public std::exception {...} 

El truco es que todo impulso :: casos pitón :: class_ tienen una referencia al tipo de objeto que se puede acceder a través de su ptr() función. Usted puede obtener este como se registre la clase con impulso :: pitón de este modo:

class_<MyCPPException> myCPPExceptionClass("MyCPPException"...); 
PyObject *myCPPExceptionType=myCPPExceptionClass.ptr(); 
register_exception_translator<MyCPPException>(&translateFunc); 

Por último, cuando se traduce la excepción de C++ a una excepción de Python, lo hace de la siguiente manera:

void translate(MyCPPException const &e) 
{ 
    PyErr_SetObject(myCPPExceptionType, boost::python::object(e).ptr()); 
} 

Aquí es un ejemplo de trabajo completo:

#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); 
} 

Este es el código 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 
4

La respuesta dada por Jack Edmonds define una clase de "excepción" de Python que no hereda Exception (o cualquier otra clase de excepción de Python incorporada). Así que a pesar de que puede ser capturado con

except my_cpp_extension.MyCPPException as e: 
    ... 

no puede ser atrapado con la captura de costumbre todo

except Exception as e: 
    ... 

Here es cómo crear una clase de excepción Python personalizado que hace hereda Exception.

+0

Pero esto no envuelve una clase existente de C++ derivada de std :: exception ... ¿o me falta algo? Si no, su solución no responde la pregunta en este hilo –

+0

@Dan Niero: La forma normal de "exportar" una excepción de C++ a Python no es para envolverlo, sino para traducirlo a una excepción de Python derivada de 'Exception'. – user763305

+0

Veo tu punto. Pero si es el lado de C++ el que genera/lanza una excepción, ¿cuál es la mejor solución para detectar esa excepción en Python? En el ejemplo aquí, puedo ver una excepción arrojada desde el código C++. Sin embargo, no puedo plantear esa excepción desde python. Solo puedo atraparlo. Si no estoy equivocado, en su solución, se da una manera de plantear una excepción de C++ de python, pero no hace que python "tenga conocimiento" de la excepción planteada desde el código de C++. En realidad lo es, pero cree que todos son RuntimeError. Disculpe si me falta algo, solo trato de entender –

1

Gracias a las plantillas variadic y captura lambda generalizada, podemos colapsar Jack Edmond's answer en algo mucho más manejable y ocultar toda la costra 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; 
} 

Exponer MyCPPException como una excepción, sólo tiene que 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 que estamos de vuelta a las sutilezas de Boost.Python: no necesita nombrar la instancia class_, no necesita este extra PyObject*, y no necesita una función extra en alguna parte.

+0

Intenté su solución y obtuve el siguiente error en el lado de Python: 'SystemError: excepción no una subclase BaseException', y' TypeError: capturar clases que no heredan de BaseException no está permitido'. Boost.Python V1.61, Python 3.4. –

Cuestiones relacionadas