2010-02-14 7 views
22

Je vous écris actuellement une extension C++ pour Python en utilisant Boost.Python. Une fonction dans cette extension peut générer une exception contenant des informations sur l'erreur (au-delà d'une chaîne lisible par un humain décrivant ce qui s'est passé). J'espérais pouvoir exporter cette exception vers Python pour que je puisse l'attraper et faire quelque chose avec les informations supplémentaires.boost :: python Exception d'exportation personnalisée

Par exemple:

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

Malheureusement Boost.Python semble traduire toutes les exceptions C (qui sont de sous-classes de std::exception) dans RuntimeError. Je me rends compte que Boost.Python permet un à mettre en œuvre cependant, il faut utiliser PyErr_SetObject qui prend PyObject* (pour le type de l'exception) et un PyObject* (pour la valeur de l'exception) traduction d'exception personnalisée - ni que je sais comment obtenir de mes classes Boost.Python. Il y a peut-être un moyen (ce qui serait génial) que je n'ai tout simplement pas encore trouvé. Sinon, quelqu'un sait-il comment exporter une exception C++ personnalisée pour que je puisse l'attraper en Python?

+1

** Bonne question & réponse! ** Il a sauvé ma journée! Je vous remercie. –

+0

Excellent! très utile ici aussi! Je voudrais 5x le voter si je pouvais :) –

Répondre

25

La solution est de créer votre classe d'exception comme tout normal ++ classe

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

L'astuce est que tous les boost :: python :: instances class_ tiennent une référence au type de l'objet qui est accessible par leur ptr() fonction. Vous pouvez obtenir ce que vous enregistrez la classe avec boost :: python comme ceci:

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

Enfin, lorsque vous traduisez le C++ exception à une exception Python, vous le faites comme suit:

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

Voici un exemple de travail complet:

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

Voici le code Python qui appelle l'extension:

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 réponse donnée par Jack Edmonds définit une classe « exception » Python qui ne hérite pas Exception (ou tout autre classe intégrée d'exception Python). Donc, bien qu'il puisse être pris avec

except my_cpp_extension.MyCPPException as e: 
    ... 

il ne peut pas être pris avec la prise habituelle tout

except Exception as e: 
    ... 

Here est comment créer un Python personnalisé classe d'exception que -t Hériter Exception.

+0

Mais cela n'emballe pas une classe C++ existante dérivée de std :: exception ... ou est-ce que je manque quelque chose? Si je ne le suis pas, votre solution ne répond pas vraiment à la question dans ce fil –

+0

@Dan Niero: La manière normale d '"exporter" une exception de C++ vers Python n'est pas de l'enrouler, mais de la traduire en une exception Python dérivée de 'Exception'. – user763305

+0

Je vois ce que vous voulez dire. Mais si c'est le côté C++ qui soulève/lance une exception, quelle est la meilleure solution pour attraper cette exception en Python? Dans cet exemple, je peux capturer une exception provenant du code C++. Cependant, je ne peux pas lever cette exception depuis python. Je peux seulement l'attraper. Si je ne me trompe pas, dans votre solution, vous donnez un moyen de déclencher une exception C++ à partir de python, mais cela ne rend pas python "conscient" de l'exception levée du code C++. En fait, il est, mais il pense qu'ils sont tous RuntimeError. Excusez-moi si quelque chose me manque, j'essaie juste de comprendre –

1

Merci aux modèles variadique et capture généralisée lambda, on peut s'effondrer Jack Edmond's answer quelque chose de beaucoup plus facile à gérer et cacher tous les cochonneries de l'utilisateur:

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

Pour exposer MyCPPException comme une exception, il vous suffit de changer py::class_ dans les liaisons à exception_:

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

Et maintenant nous sommes de retour aux finesses de Boost.Python: n'a pas besoin de nommer l'instance class_, n'a pas besoin de ce PyObject* supplémentaire, et n'a pas besoin d'une fonction supplémentaire quelque part.

+0

J'ai essayé votre solution et ai obtenu l'erreur suivante du côté de Python: 'SystemError: exception pas une sous-classe BaseException', et' TypeError: attraper des classes qui n'héritent pas de BaseException n'est pas permis'. Boost.Python V1.61, Python 3.4. –

Questions connexes