2010-02-14 7 views
22

Attualmente sto scrivendo un'estensione C++ per Python usando Boost.Python. Una funzione in questa estensione può generare un'eccezione contenente informazioni sull'errore (oltre a una stringa leggibile dall'utente che descrive cosa è successo). Speravo di poter esportare questa eccezione in Python così ho potuto prenderlo e fare qualcosa con le informazioni extra.boost :: python Esporta eccezione personalizzata

Ad esempio:

import my_cpp_module 
try: 
    my_cpp_module.my_cpp_function() 
except my_cpp_module.MyCPPException, e: 
    print e.my_extra_data 

Purtroppo Boost.Python sembra tradurre eccezioni tutte ++ C (che sono sottoclassi di std::exception) in RuntimeError. Mi rendo conto che Boost.Python consente di implementare la traduzione di eccezioni personalizzate, tuttavia è necessario utilizzare PyErr_SetObject che prende uno PyObject* (per il tipo di eccezione) e uno PyObject* (per il valore dell'eccezione) - nessuno dei quali so come ottenere da le mie classi Boost.Python. Forse c'è un modo (che sarebbe fantastico) che semplicemente non ho ancora trovato. Altrimenti qualcuno sa come esportare un'eccezione C++ personalizzata in modo che possa prenderlo in Python?

+1

** Buona domanda e risposta! ** Mi ha salvato la giornata! Grazie. –

+0

Eccellente! molto utile anche qui! Vorrei votare 5x se potessi :) –

risposta

25

La soluzione è quella di creare una classe di eccezione come qualsiasi normale classe C++

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

Il trucco è che tutti boost :: pitone :: class_ casi in possesso di un riferimento al tipo di oggetto che è accessibile attraverso il loro PTR() funzione. È possibile ottenere questo ti sarai iscritto la classe con boost :: pitone in questo modo:

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

Infine, quando si sta traducendo l'eccezione C++ per un'eccezione Python, lo fate come segue:

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

Ecco un esempio di lavoro 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); 
} 

Ecco il codice Python che chiama l'estensione:

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 risposta data da Jack Edmonds definisce una classe "eccezione" Python che non eredita Exception (o qualsiasi altra classe di eccezione Python incorporata). Quindi, anche se si può raggiungere con

except my_cpp_extension.MyCPPException as e: 
    ... 

non possono essere catturati con la solita fermo tutto

except Exception as e: 
    ... 

Here è come creare una classe di eccezione Python personalizzata che fa ereditare Exception.

+0

Ma questo non avvolge una classe C++ esistente derivata da std :: exception ... o mi manca qualcosa? Se non lo sono, la tua soluzione non risponde veramente alla domanda in questa discussione –

+0

@Dan Niero: il modo normale di "esportare" un'eccezione da C++ a Python non è di avvolgerlo, ma di tradurlo in un'eccezione Python derivata da "Eccezione". – user763305

+0

Vedo il tuo punto. Ma se è il lato C++ a sollevare/lanciare un'eccezione, qual è la soluzione migliore per catturare quell'eccezione in Python? Nell'esempio qui posso rilevare un'eccezione generata dal codice C++. Tuttavia, non posso sollevare quell'eccezione da python. Posso solo prenderlo. Se non sbaglio, nella tua soluzione, dai un modo per generare un'eccezione C++ da python, ma non rende Python "consapevole" dell'eccezione sollevata dal codice C++. In realtà lo è, ma pensa che siano tutti RuntimeError. Scusami se mi manca qualcosa, sto solo cercando di capire –

1

Grazie a modelli variadic e cattura lambda generalizzata, siamo in grado di collassare Jack Edmond's answer in qualcosa di molto più gestibile e nascondere tutte le cruft da parte dell'utente:

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

per esporre MyCPPException come eccezione, è sufficiente cambiare py::class_ nelle associazioni per exception_:

exception_<MyCPPException>("MyCPPException", py::init<std::string, std::string>()) 
    .add_property("message", &MyCPPException::getMessage) 
    .add_property("extra_data", &MyCPPException::getExtraData) 
; 

E ora siamo di nuovo alle sottigliezze di Boost.Python: non è necessario nominare l'istanza class_, non è necessario questo extra PyObject* e non è necessaria una funzione extra da qualche parte.

+0

Ho provato la tua soluzione e ho ottenuto il seguente errore sul lato Python: 'SystemError: exception non una sottoclasse di BaseException', e' TypeError: le classi che non ereditano da BaseException non sono consentite'. Boost.Python V1.61, Python 3.4. –