2013-02-21 17 views
22

Situazionedinamicamente rethrowing auto-definita C++ come eccezioni Python utilizzando SWIG

eccezioni Voglio creare un linguaggio Python vincolante per un'API C++ utilizzando SWIG. Alcune delle funzioni API possono generare eccezioni. impiego Il C++ ha una gerarchia di eccezioni autodefiniti, come in questo esempio:

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

il comportamento desiderato è la seguente:

  1. Tutti i tipi di eccezione devono avere una classe corrispondente Python come involucro. Queste classi wrapper devono essere eccezioni Python valide.

  2. Quando una chiamata API genera un'eccezione C++, dovrebbe essere colto. Il corrispondente eccezione pitone (cioè la classe involucro della eccezione intercettata C++) dovrebbe essere gettati.

  3. Questo dovrebbe essere un processo dinamico: il Python tipo eccezione viene deciso in fase di esecuzione, solo in base al tipo di esecuzione dell'eccezione catturato C++. In questo modo, non è necessario descrivere la gerarchia di eccezioni completa nel file di interfaccia SWIG.

problemi e domande

  1. classi wrapper sono eccezioni Python.

    Mentre SWIG crea le classi wrapper per tutte le eccezioni autodefinite (come per qualsiasi altra classe), queste classi non sono eccezioni Python. Il wrapper dell'eccezione di base (API::Exception nell'esempio) estende Object anziché BaseException, la classe Python di cui devono essere derivate tutte le eccezioni in Python.

    Inoltre, non sembra possibile consentire a SWIG di aggiungere manualmente una classe genitore. Nota questo è possibile quando si utilizza SWIG con Java attraverso l'uso di %typemap(javabase) (vedere SWIG documentation per i dettagli).

  2. In che modo lo Python C API genera un'eccezione definita dall'utente?

    Il modo più comune per lanciare un'eccezione Python dall'API Python C è chiamando PyErr_SetString[reference]. Questo è anche mostrato nell'applicazione demo qui sotto.

    Ma questo è solo banale con le eccezioni standard (built-in) di Python, perché i riferimenti ad essi sono memorizzati in variabili globali [reference] nell'API Python C.

    So che esiste un metodo PyErr_NewException[reference] per ottenere riferimenti a eccezioni auto-definite, ma non ho funzionato.

  3. Come può il Python C API valutare il tipo C++ in fase di esecuzione e quindi trovare la classe di wrapper Python corrispondente per nome?

    Suppongo che una classe Python possa essere ricercata per nome in fase di esecuzione, tramite lo reflection part dell'API Python C. È questa la strada da percorrere? E come è fatto nella pratica?

applicazione demo

Per sperimentare questo problema, sono generati un'API C++ con una singola funzione che calcola il fattoriale di un numero. Ha una gerarchia delle eccezioni auto-definita minima, costituita da una sola classe TooBigException.

Nota questa eccezione funge da eccezione di base nel problema generale e l'applicazione dovrebbe funzionare con qualsiasi sottoclasse di esso. Ciò significa che la soluzione può utilizzare solo il tipo dinamico (cioè il runtime) dell'eccezione rilevata per ricrearlo in Python (vedi sotto).

Il codice sorgente completo dell'applicazione demo è la seguente:

// 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; 
} 
} 

Per ottenere il Python vincolante Io uso il seguente file di interfaccia 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" 

Così ogni chiamata al API è avvolto da un blocco try-catch. Le prime eccezioni del nostro tipo di base vengono catturate e gestite. Quindi tutte le altre eccezioni vengono catturate e riconvertite usando la libreria di eccezioni SWIG.

Nota qualsiasi sottoclasse di numbers::TooBigException 'colto ei involucri della loro dinamica (vale a dire runtime) di tipo dovrebbero essere gettati, non l'involucro del loro statica (cioè tempo di compilazione) tipo, che è sempre TooBigException!

Il progetto può essere costruita facilmente eseguendo i seguenti comandi su una macchina Linux:

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

implementazione attuale

mio attuale implementazione ancora (con successo) genera un'eccezione standard di Python fisso. Il codice <TODO> sopra viene poi sostituito dal seguente:

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

che dà il seguente comportamento (expected) in Python:

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

Non sono veramente sicuro di quello che vuoi, ma hai guardato se questo codice aiuta nel tuo secondo caso? 'buttare;' – PlasmaHH

risposta

9

Sembra che qualcuno ha risposto alla tua domanda di base nel corso della lista sorso-user ...

Questo presume che tu abbia generato wrapper per le tue classi di eccezioni, credo.

+0

Questo effettivamente lo fa. La vergogna OP non ha accettato la tua risposta. – wombat

+1

Questa è davvero una parte della risposta. Tuttavia, come indicato chiaramente nella domanda, mi piacerebbe usare il tipo _dynamic_, mentre questa soluzione fa un cast statico. In altre parole: qualsiasi sottoclasse di MyException verrà catturata e modificata in MyException e tutte le informazioni di sottoclasse andranno perse. – tbacker

5

Esempio per la gerarchia di

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

Corporatura:

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) 

uscita è:

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 
________________ 

Sulla base answer in maillist.

Problemi correlati