2009-09-13 11 views
38

Quiero incrustar python en mi aplicación C++. Estoy usando la biblioteca de Boost, una gran herramienta. Pero tengo un problema.Cómo obtener el texto de excepción de Python

Si la función python arroja una excepción, quiero capturarla e imprimir un error en mi aplicación u obtener información detallada como el número de línea en el script de Python que causó el error.

¿Cómo puedo hacerlo? No puedo encontrar ninguna función para obtener información de excepción detallada en Python API o Boost.

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() sólo imprime texto de error a stderr y borra error por lo que no puede ser solución

Respuesta

41

Bueno, descubrí cómo hacerlo.

Sin impulso (sólo mensaje de error, ya que el código para extraer información de rastreo es demasiado pesado para publicar aquí):

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

y aumentar versión

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

Impresionante, esto es exactamente lo que he estado buscando ... funciona muy bien. –

+0

Esto es bueno. He descubierto en algunos casos (para mí, un impulso;: python :: importación de algo que no está en mi PYTHONPATH) que el retroceso será 0, así que protegería contra el uso de un ptraceback si es 0. Además, ¿puedes comentar sobre lo que podemos hacer con extype? Supongo que imprimir el texto del tipo de excepción de python es significativo. ¿Como hacemos eso? –

+2

Una pregunta adicional: ¿no estamos filtrando la memoria en la parte superior? ¿Qué libera los objetos devueltos por PyErr_Fetch? (No estoy seguro acerca de los casos de CPython y boost :: pythoon) – elmo

4

En el API Python C PyObject_Str, devuelve un nuevo referencia a un objeto de cadena Python con la forma de cadena del objeto Python que está pasando como argumento, al igual que str(o) en código Python. Tenga en cuenta que el objeto de excepción no tiene "información como número de línea", eso está en el objeto traceback (puede usar PyErr_Fetch para obtener el objeto de excepción y el objeto de rastreo). No sé qué (si es que algo) proporciona Boost para facilitar el uso de estas funciones específicas de C API, pero, en el peor de los casos, siempre podría recurrir a estas funciones, ya que se ofrecen en la API C.

+0

muchas gracias, Alex. Estaba buscando una forma de hacerlo sin una llamada directa de PyAPI - creo que Boost puede lidiar con excepciones, pero Boost no puede :( –

+2

@Anton, me alegro de haber ayudado, ¿qué hay de votando y aceptando esta respuesta? -) Use la icono de marca de verificación debajo del número de votos hacia arriba para esta respuesta (actualmente 0 ;-). –

18

Este es el método más robusto He podido llegar hasta ahora:

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

Parece que está bien pasar un identificador vacío a 'format_exception', por lo que no necesita el caso'! Tb'. – uckelman

+1

Esta solución funciona muy bien, pero deberá llamar a 'PyErr_NormalizeException (& exc, & val, &tb);' como [esta respuesta] (http://stackoverflow.com/a/16806477/3524982) dice. – DJMcMayhem

Cuestiones relacionadas