2010-02-14 6 views
22

Ich schreibe gerade eine C++ Erweiterung für Python mit Boost.Python. Eine Funktion in dieser Erweiterung kann eine Ausnahme erzeugen, die Informationen über den Fehler enthält (nicht nur eine von Menschen lesbare Zeichenfolge, die beschreibt, was passiert ist). Ich hatte gehofft, ich könnte diese Ausnahme nach Python exportieren, damit ich sie fangen und etwas mit den zusätzlichen Informationen machen konnte.boost :: python Export Benutzerdefinierte Ausnahme

Zum Beispiel:

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

Leider Boost.Python scheint alle C++ Ausnahmen zu übersetzen in RuntimeError (die Subklassen von std::exception sind). Ich weiß, dass Boost.Python ermöglicht benutzerdefinierte Exception Translation zu implementieren, jedoch muss man PyErr_SetObject verwenden, die eine PyObject* (für den Typ der Ausnahme) und eine PyObject* (für den Wert der Ausnahme) - von denen ich weiß nicht, woher ich komme meine Boost.Python-Klassen. Vielleicht gibt es einen Weg (was großartig wäre), den ich noch nicht gefunden habe. Kann jemand sonst eine benutzerdefinierte C++ - Ausnahme exportieren, damit ich sie in Python abfangen kann?

+1

** Gute Frage und Antwort! ** Es rettete meinen Tag! Vielen Dank. –

+0

Ausgezeichnet! sehr nützlich auch hier! Ich würde es 5x abstimmen, wenn ich könnte :) –

Antwort

25

Die Lösung ist Ihre Ausnahmeklasse wie jeder normale C++ Klasse

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

Der Trick zu schaffen ist, dass alle boost :: Python :: class_ Instanzen eine Referenz auf die Art des Objekts zu halten, die durch ihre ptr zugänglich ist() Funktion. Sie können dies, wie Sie die Klasse mit boost :: Python registrieren wie so:

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

Schließlich, wenn Sie die C++ Ausnahme von einer Python Ausnahme übersetzen, tun Sie dies wie folgt:

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

Hier ist ein voll funktionierendes Beispiel:

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

Hier ist der Python-Code, der die Erweiterung ruft:

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

Die Antwort von Jack Edmonds definiert eine Python "Exception" -Klasse, die Exception (oder eine andere integrierte Python-Exception-Klasse) nicht erbt. Obwohl es also mit

gefangen werden können
except my_cpp_extension.MyCPPException as e: 
    ... 

kann es nicht mit dem üblichen Beifang gefangen werden alle

except Exception as e: 
    ... 

Here ist, wie eine benutzerdefinierte Python Exception-Klasse zu erstellen, dass tut erben Exception.

+0

Aber das wickelt nicht eine vorhandene C++ - Klasse abgeleitet von std :: ausnahme ... oder fehlt mir etwas? Wenn ich nicht bin, beantwortet Ihre Lösung nicht wirklich die Frage in diesem Thread –

+0

@Dan Niero: Der normale Weg, eine Ausnahme von C++ nach Python zu "exportieren", ist es nicht zu wickeln, sondern in eine abgeleitete Python-Ausnahme zu übersetzen von "Ausnahme". – user763305

+0

Ich verstehe deinen Standpunkt. Aber wenn es die C++ - Seite ist, die eine Ausnahme auslöst/wirft, was ist die beste Lösung, um diese Ausnahme in Python zu fangen? In dem Beispiel hier kann ich eine Ausnahme abfangen, die aus dem C++ Code geworfen wird. Ich kann diese Ausnahme jedoch nicht innerhalb von Python auslösen. Ich kann es nur fangen. Wenn ich nicht falsch bin, in Ihrer Lösung, geben Sie eine Möglichkeit, eine C++ Ausnahme von Python zu erheben, aber es macht Python "nicht bewusst" der Ausnahme aus dem C++ - Code ausgelöst. Eigentlich ist es das, aber es denkt, dass sie alle RuntimeError sind. Entschuldigung, wenn ich etwas verpasse, versuche ich nur zu verstehen –

1

Dank variadische Vorlagen und generali Lambda-Erfassung, wir Jack Edmond's answer in etwas viel mehr überschaubar und verstecken alle der cruft vom Benutzer zusammenbrechen kann:

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

MyCPPException als Ausnahme zu belichten, Sie müssen nur ändern py::class_ in den Bindungen zu exception_:

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

Und jetzt sind wir auf die Feinheiten der Boost-zurück.Python: muss die class_ Instanz nicht benennen, benötigt dieses Extra nicht PyObject*, und benötigt keine zusätzliche Funktion irgendwo.

+0

Ich versuchte Ihre Lösung und erhielt den folgenden Fehler auf der Python-Seite: 'SystemError: Ausnahme keine BaseException-Unterklasse' und' TypeError: das Erfassen von Klassen, die nicht von BaseException erben, ist nicht erlaubt'. Boost.Python V1.61, Python 3.4. –