2009-09-13 24 views
38

Ich möchte Python in meine C++ - Anwendung einbetten. Ich benutze Boost-Bibliothek - tolles Werkzeug. Aber ich habe ein Problem.Wie bekomme ich Python-Ausnahmetext

Wenn Python-Funktion eine Ausnahme auslöst, möchte ich es abfangen und Fehler in meiner Anwendung drucken oder einige detaillierte Informationen wie Zeilennummer in Python-Skript, die Fehler verursacht.

Wie kann ich es tun? Ich kann keine Funktionen finden, um detaillierte Ausnahmeinformationen in Python API oder Boost zu erhalten.

try { 
module=import("MyModule"); //this line will throw excetion if MyModule contains an error 
} catch (error_already_set const &) { 
//Here i can said that i have error, but i cant determine what caused an error 
std::cout << "error!" << std::endl; 
} 

PyErr_Print() nur Fehlertext zu Stderr druckt und löschen Fehler so dass es nicht

Lösung sein kann

Antwort

41

Nun, ich habe herausgefunden, wie es geht.

ohne Boost (nur Fehlermeldung, weil Code Infos von Traceback zu extrahieren ist zu schwer es hier zu posten):

PyObject *ptype, *pvalue, *ptraceback; 
PyErr_Fetch(&ptype, &pvalue, &ptraceback); 
//pvalue contains error message 
//ptraceback contains stack snapshot and many other information 
//(see python traceback structure) 

//Get error message 
char *pStrErrorMessage = PyString_AsString(pvalue); 

Und BOOST Version

try{ 
//some code that throws an error 
}catch(error_already_set &){ 

    PyObject *ptype, *pvalue, *ptraceback; 
    PyErr_Fetch(&ptype, &pvalue, &ptraceback); 

    handle<> hType(ptype); 
    object extype(hType); 
    handle<> hTraceback(ptraceback); 
    object traceback(hTraceback); 

    //Extract error message 
    string strErrorMessage = extract<string>(pvalue); 

    //Extract line number (top entry of call stack) 
    // if you want to extract another levels of call stack 
    // also process traceback.attr("tb_next") recurently 
    long lineno = extract<long> (traceback.attr("tb_lineno")); 
    string filename = extract<string>(traceback.attr("tb_frame").attr("f_code").attr("co_filename")); 
    string funcname = extract<string>(traceback.attr("tb_frame").attr("f_code").attr("co_name")); 
... //cleanup here 
+1

Super, das ist genau das, was ich gesucht habe ... funktioniert super. –

+0

Das ist schön. Ich habe in einigen Fällen entdeckt (für mich, ein Boost;: Python :: Import von etwas nicht in meinem PYTHONPATH) ptraceback wird 0, also würde ich gegen die Verwendung eines Ptraceback schützen, wenn es 0 ist. Auch können Sie Kommentar zu dem, was wir mit Extype machen können? Ich denke, das Drucken des Textes des Python-Ausnahmetyps ist sinnvoll. Wie machen wir das? –

+2

Eine zusätzliche Frage: Sind wir nicht Speicher in oben undicht? Was gibt Objekte frei, die von PyErr_Fetch zurückgegeben werden? (Ich bin mir nicht sicher sowohl über CPython und Boost :: Python Fällen) – elmo

4

im Python C API, PyObject_Str gibt eine neue Referenz auf ein Python String-Objekt mit der Form einer Zeichenfolge des Python-Objekts, das Sie als Argument übergeben - genau wie str(o) im Python-Code. Beachten Sie, dass das Ausnahmeobjekt keine "Informationen wie Zeilennummer" hat - das ist im Traceback Objekt (Sie können PyErr_Fetch verwenden, um sowohl das Ausnahmeobjekt als auch das Traceback-Objekt abzurufen). Ich weiß nicht, was (wenn überhaupt) Boost bietet, um diese spezifischen C-API-Funktionen einfacher zu verwenden, aber im schlimmsten Fall könnten Sie immer auf diese Funktionen zurückgreifen, wie sie in der C-API selbst angeboten werden.

+0

vielen Dank, Alex. Ich habe nach einem Weg gesucht, um es ohne direkten Aufruf von PyAPI zu machen - ich denke, Boost kann mit Ausnahmen umgehen, aber Boost kann nicht :( –

+2

@Anton, froh, dass ich geholfen habe, also was ist mit Upvoting und Annahme dieser Antwort? -) Benutze die Häkchensymbol unter der Anzahl der Upvotes für diese Antwort (derzeit 0 ;-). –

18

Dies ist das robusteste Verfahren Ich konnte so weit kommen:

try { 
     ... 
    } 
    catch (bp::error_already_set) { 
     if (PyErr_Occurred()) { 
      msg = handle_pyerror(); 
     } 
     py_exception = true; 
     bp::handle_exception(); 
     PyErr_Clear(); 
    } 
    if (py_exception) 
    .... 


// decode a Python exception into a string 
std::string handle_pyerror() 
{ 
    using namespace boost::python; 
    using namespace boost; 

    PyObject *exc,*val,*tb; 
    object formatted_list, formatted; 
    PyErr_Fetch(&exc,&val,&tb); 
    handle<> hexc(exc),hval(allow_null(val)),htb(allow_null(tb)); 
    object traceback(import("traceback")); 
    if (!tb) { 
     object format_exception_only(traceback.attr("format_exception_only")); 
     formatted_list = format_exception_only(hexc,hval); 
    } else { 
     object format_exception(traceback.attr("format_exception")); 
     formatted_list = format_exception(hexc,hval,htb); 
    } 
    formatted = str("\n").join(formatted_list); 
    return extract<std::string>(formatted); 
} 
+1

Es ist offensichtlich in Ordnung, einen leeren Handle an 'format_exception' zu übergeben, so dass Sie den'! Tb'-Fall nicht benötigen. – uckelman

+1

Diese Lösung funktioniert gut, bu müssen Sie 'PyErr_NormalizeException aufrufen (& exc, & val, &tb);' wie [diese Antwort] (http://stackoverflow.com/a/16806477/3524982) sagt. – DJMcMayhem