usar try tipos son recent que most manejo last las instrucciones hacer excepciones ejemplos diferentes como catch c++ python exception reflection swig

c++ - try - traceback(most recent call last) python



Reenvío dinámico de excepciones C++ autodefinidas como excepciones de Python usando SWIG (2)

Situación

Quiero crear un enlace de lenguaje Python para una API de C ++ utilizando SWIG. Algunas de las funciones de la API pueden lanzar excepciones. La aplicación C ++ tiene una jerarquía de excepciones autodefinidas, como este ejemplo:

std::exception -> API::Exception -> API::NetworkException -> API::TimeoutException -> API::UnreachableException -> API::InvalidAddressException

El comportamiento deseado es el siguiente:

  1. Todos los tipos de excepción deben tener una clase Python coincidente como contenedor . Estas clases de envoltorio deben ser excepciones válidas de Python .

  2. Cuando una llamada a la API lanza una excepción de C ++ , se debe capturar . La excepción correspondiente de Python (es decir, la clase envoltura de la excepción de C ++ capturada) debe lanzarse .

  3. Este debe ser un proceso dinámico: el tipo de excepción de Python se decide en tiempo de ejecución , solo en función del tipo de tiempo de ejecución de la excepción de C ++ capturada. De esta manera, no es necesario describir la jerarquía completa de excepciones en el archivo de interfaz SWIG.

Problemas y preguntas

  1. Las clases de envoltorio no son excepciones de Python.

    Mientras SWIG crea clases de envoltorio para todas las excepciones autodefinidas (como para cualquier otra clase), estas clases no son excepciones de Python. El envoltorio de la excepción base ( API::Exception en el ejemplo) extiende Object lugar de BaseException , la clase de Python de la que se deben derivar todas las excepciones en Python.

    Además, no parece posible permitir que SWIG agregue una clase principal manualmente. Tenga en cuenta que esto es posible cuando se utiliza SWIG con Java mediante el uso de %typemap(javabase) (consulte la documentación de SWIG para obtener más información).

  2. ¿Cómo puede la API de Python C lanzar una excepción definida por el usuario?

    La forma más común de lanzar una excepción de Python desde la API de Python C es llamar a PyErr_SetString [reference] . Esto también se muestra en la aplicación de demostración a continuación.

    Pero esto es solo trivial con las excepciones estándar (incorporadas) de Python, porque las referencias a ellas se almacenan en variables globales [ reference ] en la API de Python C.

    Sé que hay un método PyErr_NewException [reference] para obtener referencias a excepciones autodefinidas, pero no conseguí que esto funcionara.

  3. ¿Cómo puede la API de Python C evaluar el tipo de C ++ en tiempo de ejecución y luego encontrar la clase de envoltura de Python correspondiente por nombre?

    Asumo que una clase de Python se puede buscar por nombre en tiempo de ejecución, a través de la parte de reflexión de la API de Python C. ¿Es este el camino a seguir? ¿Y cómo se hace en la práctica?

Aplicación de demostración

Para experimentar con este problema, creé una pequeña API de C ++ con una única función que calcula el factorial de un número. Tiene una jerarquía de excepciones autodefinida mínima, que consta de una sola clase TooBigException .

Tenga en cuenta que esta excepción actúa como la excepción base en el problema general y la aplicación debería funcionar con cualquier subclase de la misma. Esto significa que la solución solo puede usar el tipo dinámico (es decir, el tiempo de ejecución) de la excepción capturada para volver a lanzarla en Python (ver más abajo).

El código fuente completo de la aplicación de demostración es el siguiente:

// File: numbers.h namespace numbers { int fact(int n); } // File: numbers.cpp #include "TooBigException.h" namespace numbers { int fact(int n) { if (n > 10) throw TooBigException("Value too big", n); else if (n <= 1) return 1; else return n*fact(n-1); } } // File: TooBigException.h namespace numbers { class TooBigException: public std::exception { public: explicit TooBigException(const std::string & inMessage, const int inValue); virtual ~TooBigException() throw() {} virtual const char* what() const throw(); const std::string & message() const; const int value() const; private: std::string mMessage; int mValue; }; } // File: TooBigException.cpp #include "TooBigException.h" namespace numbers { TooBigException::TooBigException(const std::string & inMessage, const int inValue): std::exception(), mMessage(inMessage), mValue(inValue) { } const char* TooBigException::what() const throw(){ return mMessage.c_str(); } const std::string & TooBigException::message() const { return mMessage; } const int TooBigException::value() const { return mValue; } }

Para obtener el enlace de Python utilizo el siguiente archivo de interfaz SWIG:

// File: numbers.i %module numbers %include "stl.i" %include "exception.i" %{ #define SWIG_FILE_WITH_INIT #include "TooBigException.h" #include "numbers.h" %} %exception { try { $action } catch (const numbers::TooBigException & e) { // This catches any self-defined exception in the exception hierarchy, // because they all derive from this base class. <TODO> } catch (const std::exception & e) { SWIG_exception(SWIG_RuntimeError, (std::string("C++ std::exception: ") + e.what()).c_str()); } catch (...) { SWIG_exception(SWIG_UnknownError, "C++ anonymous exception"); } } %include "TooBigException.h" %include "numbers.h"

Así que cada llamada a la API está envuelta por un bloque try-catch. Las primeras excepciones de nuestro tipo base son capturadas y manejadas. Luego, todas las demás excepciones se capturan y se vuelven a generar utilizando la biblioteca de excepciones SWIG.

Tenga en numbers::TooBigException se captura cualquier subclase de numbers::TooBigException y los envoltorios de su tipo dinámico (es decir, tiempo de ejecución) deben lanzarse , no el envoltorio de su tipo estático (es decir, tiempo de compilación), ¡que siempre es TooBigException !

El proyecto se puede construir fácilmente ejecutando los siguientes comandos en una máquina Linux:

$ swig -c++ -python numbers.i $ g++ -fPIC -shared TooBigException.cpp numbers.cpp numbers_wrap.cxx / -I/usr/include/python2.7 -o _numbers.so

Implementación actual

Mi implementación actual todavía (con éxito) lanza una excepción estándar de Python fija. El código <TODO> anterior se reemplaza por:

PyErr_SetString(PyExc_Exception, (std::string("C++ self-defined exception ") + e.what()).c_str()); return NULL;

Lo que da el siguiente comportamiento (esperado) en Python:

>>> import numbers >>> fact(11) Traceback (most recent call last): File "<stdin>", line 1, in <module> Exception: C++ self-defined exception Value too big


Ejemplo para tu jerarquía

std::exception -> API::Exception -> API::NetworkException -> API::TimeoutException -> API::UnreachableException -> API::InvalidAddressException

example.i:

%module example %include "stl.i" %include "exception.i" %{ #define SWIG_FILE_WITH_INIT #include "example.cpp" %} %{ #define CATCH_PE(Namespace,Exception) / catch(const Namespace::Exception &e) / { / SWIG_Python_Raise(SWIG_NewPointerObj(new Namespace::Exception(e), / SWIGTYPE_p_##Namespace##__##Exception,SWIG_POINTER_OWN), / #Exception, SWIGTYPE_p_##Namespace##__##Exception); / SWIG_fail; / } / /**/ // should be in "derived first" order #define FOR_EACH_EXCEPTION(ACTION) / ACTION(API,UnreachableException) / ACTION(API,TimeoutException) / ACTION(API,InvalidAddressException) / ACTION(API,NetworkException) / ACTION(API,Exception) / /**/ // In order to remove macros, need traits: // http://swig.10945.n7.nabble.com/traits-based-access-to-swig-type-info-td12315.html %} %exception { try { $action } FOR_EACH_EXCEPTION(CATCH_PE) catch (const std::exception & e) { SWIG_exception(SWIG_RuntimeError, (std::string("C++ std::exception: ") + e.what()).c_str()); } catch (...) { SWIG_exception(SWIG_UnknownError, "C++ anonymous exception"); } } %include "example.cpp"

example.cpp:

#include <exception> #include <stdexcept> namespace API { struct Exception: std::exception { virtual const char* what() const throw() { return "It is API::Exception"; } }; struct NetworkException: Exception { virtual const char* what() const throw() { return "It is API::NetworkException"; } }; struct TimeoutException: NetworkException { virtual const char* what() const throw() { return "It is API::TimeoutException"; } }; struct UnreachableException: NetworkException { virtual const char* what() const throw() { return "It is API::UnreachableException"; } }; struct InvalidAddressException: Exception { virtual const char* what() const throw() { return "It is API::InvalidAddressException"; } }; inline void select(int i) { switch(i) { case 0: throw Exception(); case 1: throw NetworkException(); case 2: throw TimeoutException(); case 3: throw UnreachableException(); case 4: throw InvalidAddressException(); default: throw std::runtime_error("It is std::runtime_error"); } } }

Construir:

swig -c++ -python example.i && g++ -fPIC -shared -lpython2.7 example.cpp example_wrap.cxx -I/usr/include/python2.7 -o _example.so

test.py:

#!/usr/bin/env python2.7 from exceptions import BaseException from example import * def catch(i): try: select(i) except UnreachableException as e: print "Caught UnreachableException" print e.what() print e except TimeoutException as e: print "Caught TimeoutException" print e.what() print e except InvalidAddressException as e: print "Caught InvalidAddressException" print e.what() print e except NetworkException as e: print "Caught NetworkException" print e.what() print e except Exception as e: print "Caught Exception" print e.what() print e except BaseException as e: print "Caught BaseException" print str(e) print "_"*16 for i in xrange(6): catch(i)

La salida es:

Caught Exception It is API::Exception <example.Exception; proxy of <Swig Object of type ''API::Exception *'' at 0x7f9f54a02120> > ________________ Caught NetworkException It is API::NetworkException <example.NetworkException; proxy of <Swig Object of type ''API::NetworkException *'' at 0x7f9f54a02120> > ________________ Caught TimeoutException It is API::TimeoutException <example.TimeoutException; proxy of <Swig Object of type ''API::TimeoutException *'' at 0x7f9f54a02120> > ________________ Caught UnreachableException It is API::UnreachableException <example.UnreachableException; proxy of <Swig Object of type ''API::UnreachableException *'' at 0x7f9f54a02120> > ________________ Caught InvalidAddressException It is API::InvalidAddressException <example.InvalidAddressException; proxy of <Swig Object of type ''API::InvalidAddressException *'' at 0x7f9f54a02120> > ________________ Caught BaseException C++ std::exception: It is std::runtime_error ________________

Basado en la respuesta en maillist .


Parece que alguien ha respondido tu pregunta básica en la lista de usuarios de swig ...

%exception { try { $action } catch (MyException &_e) { SWIG_Python_Raise(SWIG_NewPointerObj( (new MyException(static_cast<const MyException& >(_e))), SWIGTYPE_p_MyException,SWIG_POINTER_OWN), "MyException", SWIGTYPE_p_MyException); SWIG_fail; } }

Esto supone que has generado envoltorios para tus clases de excepción, creo.