2009-11-25 17 views
24

Sto riscontrando qualche problema nel trovare il modo corretto di eseguire un traceback Python utilizzando l'API C. Sto scrivendo un'applicazione che incorpora l'interprete Python. Voglio essere in grado di eseguire codice Python arbitrario e, se solleva un'eccezione, di tradurlo nella mia specifica eccezione C++ specifica per l'applicazione. Per ora, è sufficiente estrarre solo il nome del file e il numero di riga in cui è stata sollevata l'eccezione Python. Questo è quello che ho finora:Accesso a un traceback Python dall'API C

PyObject* pyresult = PyObject_CallObject(someCallablePythonObject, someArgs); 
if (!pyresult) 
{ 
    PyObject* excType, *excValue, *excTraceback; 
    PyErr_Fetch(&excType, &excValue, &excTraceback); 
    PyErr_NormalizeException(&excType, &excValue, &excTraceback); 

    PyTracebackObject* traceback = (PyTracebackObject*)traceback; 
    // Advance to the last frame (python puts the most-recent call at the end) 
    while (traceback->tb_next != NULL) 
     traceback = traceback->tb_next; 

    // At this point I have access to the line number via traceback->tb_lineno, 
    // but where do I get the file name from? 

    // ...  
} 

scavare intorno nel codice sorgente Python, vedo accedono sia il nome file e modulo nome del frame corrente attraverso la struttura _frame, che sembra che è un privately- struct definito. La mia prossima idea era caricare a livello di codice il modulo 'traceback' di Python e chiamare le sue funzioni con l'API C. È sano di mente? C'è un modo migliore per accedere a un traceback Python da C?

+0

[PyErr_Fetch] (https://docs.python.org/2/c-api/exceptions.html#c.PyErr_Fetch) produce perdita di memoria (a seconda del l'implementazione può essere importante) – alex

+0

Qual è lo scopo del traceback "PyTracebackObject * traceback = (PyTracebackObject *)"? Penso che tu intenda "PyTracebackObject * traceback = (PyTracebackObject *) excTraceback;". – aquirdturtle

risposta

8

Ho scoperto che _frame è effettivamente definito nell'intestazione frameobject.h inclusa con Python. Armati di questo, più guardando traceback.c nella realizzazione di Python C, abbiamo:

#include <Python.h> 
#include <frameobject.h> 

PyTracebackObject* traceback = get_the_traceback(); 

int line = traceback->tb_lineno; 
const char* filename = PyString_AsString(traceback->tb_frame->f_code->co_filename); 

Ma questo sembra ancora molto sporca a me.

3

Un principio che ho trovato utile nello scrivere estensioni C è quello di utilizzare ogni lingua dove è più adatto. Quindi, se si ha un compito da fare, sarebbe meglio implementarlo in Python, implementarlo in Python, e se sarebbe meglio implementarlo in C, farlo in C. Interpretare i traceback è meglio farlo in Python per due motivi: primo, perché Python ha gli strumenti per farlo, e in secondo luogo, perché non è critico per la velocità.

vorrei scrivere una funzione Python per estrarre le informazioni necessarie dal traceback, quindi chiamare da C.

Si potrebbe anche andare fino al punto di scrivere un wrapper Python per la vostra esecuzione callable. Invece di invocare someCallablePythonObject, passarlo come argomento alla funzione Python:

def invokeSomeCallablePythonObject(obj, args): 
    try: 
     result = obj(*args) 
     ok = True 
    except: 
     # Do some mumbo-jumbo with the traceback, etc. 
     result = myTraceBackMunger(...) 
     ok = False 
    return ok, result 

Poi nel codice C, chiamare questa funzione Python per fare il lavoro. La chiave qui è decidere in modo pragmatico quale lato della divisione C-Python inserire il codice.

+0

Non sono sicuro di capire come ciò possa essere d'aiuto. Non sto scrivendo un modulo di estensione, ma piuttosto l'integrazione dell'interprete. Quindi, per implementare la soluzione (se ho capito bene), dovrei scrivere un blob di codice Python e archiviarlo nel mio codice C++ come una stringa. Poi a un certo punto dovrei compilare il codice, creare una funzione, quindi chiamare la funzione tramite PyObject_CallObject. Questo sembra un sacco di lavoro rispetto al semplice esame delle strutture di frame dello stack nativo in C. – cwick

+0

Questo consiglio di dividere le lingue in modo pragmatico basato sui loro punti di forza ha molto senso, ma mi sento cauto nel tentare di eseguire Python arbitrario per elaborare lo stato di errore dall'esecuzione di qualche altro Python arbitrario. – caps

9

Questa è una vecchia domanda ma, per riferimento futuro, è possibile ottenere il frame dello stack corrente dall'oggetto dello stato del thread e quindi spostare i frame all'indietro. Un oggetto traceback non è necessario a meno che non si desideri conservare lo stato per il futuro.

Ad esempio:

PyThreadState *tstate = PyThreadState_GET(); 
if (NULL != tstate && NULL != tstate->frame) { 
    PyFrameObject *frame = tstate->frame; 

    printf("Python stack trace:\n"); 
    while (NULL != frame) { 
     // int line = frame->f_lineno; 
     /* 
     frame->f_lineno will not always return the correct line number 
     you need to call PyCode_Addr2Line(). 
     */ 
     int line = PyCode_Addr2Line(frame->f_code, frame->f_lasti); 
     const char *filename = PyString_AsString(frame->f_code->co_filename); 
     const char *funcname = PyString_AsString(frame->f_code->co_name); 
     printf(" %s(%d): %s\n", filename, line, funcname); 
     frame = frame->f_back; 
    } 
} 
9

ho preferiscono mettere in Python da C:

err = PyErr_Occurred(); 
if (err != NULL) { 
    PyObject *ptype, *pvalue, *ptraceback; 
    PyObject *pystr, *module_name, *pyth_module, *pyth_func; 
    char *str; 

    PyErr_Fetch(&ptype, &pvalue, &ptraceback); 
    pystr = PyObject_Str(pvalue); 
    str = PyString_AsString(pystr); 
    error_description = strdup(str); 

    /* See if we can get a full traceback */ 
    module_name = PyString_FromString("traceback"); 
    pyth_module = PyImport_Import(module_name); 
    Py_DECREF(module_name); 

    if (pyth_module == NULL) { 
     full_backtrace = NULL; 
     return; 
    } 

    pyth_func = PyObject_GetAttrString(pyth_module, "format_exception"); 
    if (pyth_func && PyCallable_Check(pyth_func)) { 
     PyObject *pyth_val; 

     pyth_val = PyObject_CallFunctionObjArgs(pyth_func, ptype, pvalue, ptraceback, NULL); 

     pystr = PyObject_Str(pyth_val); 
     str = PyString_AsString(pystr); 
     full_backtrace = strdup(str); 
     Py_DECREF(pyth_val); 
    } 
} 
+0

Qui mancano alcuni Py_DECREFs ... 'pystr' deve essere decrefato dopo ogni chiamata a' PyObject_Str', e 'pyth_module' deve anche essere decrefdato. –

+0

Per chiarire, questo codice è un'implementazione di myTraceBackMunger() proposta dalla risposta di Ned Batchholder. Il punto è che, anche se potrebbe essersi verificata un'eccezione, l'interprete Python può ancora essere usato, e dovrebbe essere usato perché la gestione del traceback è ad un livello più alto, non è necessario capirne i dettagli. Tuttavia, se vuoi usare C, la risposta di Jason McCampbell sembra la più semplice, senza nemmeno usare un oggetto traceback, solo la pila di frame sottostante. – bootchk

+0

Con questo codice ho ricevuto un errore quando ho chiamato 'PyObject_CallFunctionObjArgs' -' pvalue' aveva un tipo sbagliato. Quindi ho aggiunto 'PyErr_NormalizeException' dopo' PyErr_Fetch' come ha fatto Bartosz Kosarzycki nella sua risposta e ora funziona. –

2

avevo ragione di farlo di recente durante la scrittura di un inseguitore di assegnazione per NumPy. Le risposte precedenti sono vicine ma frame->f_lineno non restituirà sempre il numero di linea corretto - è necessario chiamare PyFrame_GetLineNumber(). Ecco un frammento di codice aggiornato:

#include "frameobject.h" 
... 

PyFrameObject* frame = PyEval_GetFrame(); 
int lineno = PyFrame_GetLineNumber(frame); 
PyObject *filename = frame->f_code->co_filename; 

Lo stato filo completo è consultabile nel PyFrameObject anche; se vuoi camminare nello stack continua ad iterare su f_back finché non diventa NULL. Controlla l'intera struttura dati in frameobject.h: http://svn.python.org/projects/python/trunk/Include/frameobject.h

Vedi anche: https://docs.python.org/2/c-api/reflection.html

1

ho usato il seguente codice per estrarre il corpo errore di eccezione Python. strExcType memorizza il tipo di eccezione e strExcValue memorizza il corpo dell'eccezione. valori dei campioni sono:

strExcType:"<class 'ImportError'>" 
strExcValue:"ImportError("No module named 'nonexistingmodule'",)" 

codice Cpp:

if(PyErr_Occurred() != NULL) { 
    PyObject *pyExcType; 
    PyObject *pyExcValue; 
    PyObject *pyExcTraceback; 
    PyErr_Fetch(&pyExcType, &pyExcValue, &pyExcTraceback); 
    PyErr_NormalizeException(&pyExcType, &pyExcValue, &pyExcTraceback); 

    PyObject* str_exc_type = PyObject_Repr(pyExcType); 
    PyObject* pyStr = PyUnicode_AsEncodedString(str_exc_type, "utf-8", "Error ~"); 
    const char *strExcType = PyBytes_AS_STRING(pyStr); 

    PyObject* str_exc_value = PyObject_Repr(pyExcValue); 
    PyObject* pyExcValueStr = PyUnicode_AsEncodedString(str_exc_value, "utf-8", "Error ~"); 
    const char *strExcValue = PyBytes_AS_STRING(pyExcValueStr); 

    // When using PyErr_Restore() there is no need to use Py_XDECREF for these 3 pointers 
    //PyErr_Restore(pyExcType, pyExcValue, pyExcTraceback); 

    Py_XDECREF(pyExcType); 
    Py_XDECREF(pyExcValue); 
    Py_XDECREF(pyExcTraceback); 

    Py_XDECREF(str_exc_type); 
    Py_XDECREF(pyStr); 

    Py_XDECREF(str_exc_value); 
    Py_XDECREF(pyExcValueStr); 
} 
0

È possibile accedere Python traceback simile a tb_printinternal funzione. Itera su lista PyTracebackObject. Ho provato anche i suggerimenti di cui sopra per scorrere i frame, ma non funziona per me (vedo solo l'ultimo frame dello stack).

Estratti codice CPython:

static int 
tb_displayline(PyObject *f, PyObject *filename, int lineno, PyObject *name) 
{ 
    int err; 
    PyObject *line; 

    if (filename == NULL || name == NULL) 
     return -1; 
    line = PyUnicode_FromFormat(" File \"%U\", line %d, in %U\n", 
           filename, lineno, name); 
    if (line == NULL) 
     return -1; 
    err = PyFile_WriteObject(line, f, Py_PRINT_RAW); 
    Py_DECREF(line); 
    if (err != 0) 
     return err; 
    /* ignore errors since we can't report them, can we? */ 
    if (_Py_DisplaySourceLine(f, filename, lineno, 4)) 
     PyErr_Clear(); 
    return err; 
} 

static int 
tb_printinternal(PyTracebackObject *tb, PyObject *f, long limit) 
{ 
    int err = 0; 
    long depth = 0; 
    PyTracebackObject *tb1 = tb; 
    while (tb1 != NULL) { 
     depth++; 
     tb1 = tb1->tb_next; 
    } 
    while (tb != NULL && err == 0) { 
     if (depth <= limit) { 
      err = tb_displayline(f, 
           tb->tb_frame->f_code->co_filename, 
           tb->tb_lineno, 
           tb->tb_frame->f_code->co_name); 
     } 
     depth--; 
     tb = tb->tb_next; 
     if (err == 0) 
      err = PyErr_CheckSignals(); 
    } 
    return err; 
} 
Problemi correlati