2009-11-25 27 views
24

我在使用C API來找出正確的方式來回溯Python回溯時遇到了一些麻煩。我正在編寫嵌入Python解釋器的應用程序。我希望能夠執行任意Python代碼,並且如果它引發異常,請將其轉換爲我自己的特定於應用程序的C++異常。目前,僅提取Python異常引發的文件名和行號就足夠了。這是我到目前爲止有:從C API訪問Python回溯

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? 

    // ...  
} 

在Python源代碼挖的時候,我看到他們通過_frame結構,它看起來既訪問當前幀的文件名和模塊名像是一個privately-定義的結構。我的下一個想法是以編程方式加載Python的'traceback'模塊並用C API調用它的函數。這是否理智?有沒有更好的方法來從C訪問Python追溯?

+0

[PyErr_Fetch](https://docs.python.org/2/c-api/exceptions.html#c.PyErr_Fetch)產生內存泄漏(取決於實現它可能很重要) – alex

+0

「PyTracebackObject * traceback =(PyTracebackObject *)traceback;」的目的是什麼?我認爲你的意思是「PyTracebackObject * traceback =(PyTracebackObject *)excTraceback;」。 – aquirdturtle

回答

8

我發現_frame實際上是在Python包含的frameobject.h頭文件中定義的。與此加看着Python的C實現traceback.c武裝,我們有:

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

但這似乎仍然非常髒了我。

3

我發現編寫C擴展有用的一個原則是使用每種語言最適合的地方。因此,如果您有一項任務需要在Python中實現,那麼在Python中實現,如果最好在C中實現,則在C中實現。解釋回溯最好在Python中完成,原因有二:首先,因爲Python有工具可以做到,其次,因爲它不是速度至關重要的。

我會寫一個Python函數提取你回溯需要的信息,然後從下

稱之爲

你甚至可以走這麼遠來編寫Python包裝爲您調用執行。不是調用someCallablePythonObject的,把它作爲一個參數傳遞給你的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 

然後在你的C代碼,調用這個函數的Python做的工作。這裏的關鍵是務實地決定C-Python拆分哪一邊來放置代碼。

+0

不知道我明白這是如何幫助。我不寫一個擴展模塊,而是嵌入解釋器。所以爲了實現你的解決方案(如果我理解你是對的),我將不得不編寫一段Python代碼並將它作爲字符串存儲在我的C++代碼中。然後在某些時候,我將不得不編譯代碼,從中創建一個函數,然後通過PyObject_CallObject調用函數。與僅檢查C中的本地堆棧幀結構相比,這看起來像是一大堆工作。 – cwick

+0

這種基於其優點實用地在語言之間進行劃分的建議非常有意義,但我對嘗試執行任意Python來處理從執行其他任意Python的錯誤狀態。 – caps

9

這是一個老問題,但爲了將來的參考,您可以從線程狀態對象中獲取當前堆棧幀,然後僅向後走幀。除非您想保留未來的狀態,否則回溯對象不是必需的。

例如:

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

我喜歡調入Python從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

您在這裏缺少一些Py_DECREF ...每次調用「PyObject_Str」後需要將'pystr'變爲'',並且'pyth_module'也需要被變形。 –

+0

爲了澄清,此代碼是由Ned Batchholder的答案提出的myTraceBackMunger()的實現。重點是,即使可能發生了異常,Python解釋器仍然可以使用,應該使用它,因爲它處理的追蹤級別較高,您不需要了解詳細信息。然而,如果你想使用C,Jason McCampbell的答案看起來是最簡單的答案,甚至不使用追蹤對象,也不使用底層的框架。 – bootchk

+0

有了這段代碼,我調用'PyObject_CallFunctionObjArgs'時發生錯誤 - ''pvalue'類型錯誤。所以我在'PyErr_Fetch'之後添加了'PyErr_NormalizeException',因爲Bartosz Kosarzycki在他的回答中做了,現在它就可以工作了。 –

2

我有理由最近爲此而寫numpy的分配跟蹤。以前的答案很接近,但frame->f_lineno不會總是返回正確的線路號碼 - 您需要致電PyFrame_GetLineNumber()。這是一個更新的代碼片段:

#include "frameobject.h" 
... 

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

完整的線程狀態也可以在PyFrameObject中使用;如果你想走棧,繼續迭代f_back,直到它爲NULL。檢出frameobject中的完整數據結構。H:http://svn.python.org/projects/python/trunk/Include/frameobject.h

參見:https://docs.python.org/2/c-api/reflection.html

1

我用下面的代碼中提取Python異常的錯誤的身體strExcType存儲異常類型,strExcValue存儲異常體。樣本值:

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

.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

您可以訪問類似於tb_printinternal功能Python的回溯。它遍歷PyTracebackObject列表。我也嘗試過上面的建議來遍歷幀,但它不適用於我(我只看到最後一個堆棧幀)。從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; 
}