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:
Tutti i tipi di eccezione devono avere una classe corrispondente Python come involucro. Queste classi wrapper devono essere eccezioni Python valide.
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.
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
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) estendeObject
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).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.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
Non sono veramente sicuro di quello che vuoi, ma hai guardato se questo codice aiuta nel tuo secondo caso? 'buttare;' – PlasmaHH