【问题标题】:How to get Python exception text如何获取 Python 异常文本
【发布时间】:2010-11-27 22:18:04
【问题描述】:

我想在我的 C++ 应用程序中嵌入 python。我正在使用 Boost 库 - 很棒的工具。但我有一个问题。

如果 python 函数抛出异常,我想捕获它并在我的应用程序中打印错误或获取一些详细信息,例如导致错误的 python 脚本中的行号。

我该怎么做?我在 Python API 或 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() 只是将错误文本打印到 stderr 并清除错误,因此无法解决

【问题讨论】:

    标签: c++ python exception boost-python


    【解决方案1】:

    好吧,我知道怎么做。

    没有提升(只有错误消息,因为从回溯中提取信息的代码太重,无法在此处发布):

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

    还有 BOOST 版本

    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
    

    【讨论】:

    • 太棒了,这正是我一直在寻找的……效果很好。
    • 这很好。我发现在某些情况下(对我来说,不在我的 PYTHONPATH 中的某些东西的 boost;:python::import)ptraceback 将为 0,所以如果它为 0,我会防止使用 ptraceback。另外,你能评论我们可以用 extype 做什么?我想打印 python 异常类型的文本是有意义的。我们该怎么做?
    • 另外一个问题:我们不是在上面泄漏内存吗?什么释放 PyErr_Fetch 返回的对象? (我不确定 CPython 和 boost::pythoon 案例)
    • 我肯定会对提取回溯的非增强代码感兴趣。或者只是对结构的一些描述,我似乎找不到文档。
    • 根据doc,ptype pvalue ptraceback可以是没有内容的,null,甚至pyobject指针本身也不为null。
    【解决方案2】:

    这是迄今为止我能想到的最可靠的方法:

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

    【讨论】:

    • 显然可以将空句柄传递给format_exception,因此您不需要!tb 的情况。
    • 这个解决方案效果很好,但是你需要像this answer 说的那样打电话给PyErr_NormalizeException(&amp;exc, &amp;val, &amp;tb);
    【解决方案3】:

    在 Python C API 中,PyObject_Str 以您作为参数传递的 Python 对象的字符串形式返回对 Python 字符串对象的新引用——就像 Python 代码中的 str(o)。请注意,异常对象没有“行号等信息”——位于 traceback 对象中(您可以使用PyErr_Fetch 获取异常对象和回溯对象)。不知道 Boost 提供了什么(如果有的话)来使这些特定的 C API 函数更易于使用,但在最坏的情况下,您总是可以求助于这些 C API 本身提供的函数。

    【讨论】:

    • 非常感谢,亚历克斯。我正在寻找一种不直接调用 PyAPI 的方法——我认为 Boost 可以处理异常,但 Boost 不能:(
    • @Anton,很高兴我提供了帮助,那么点赞和接受这个答案怎么样?-) 使用投票数下方的复选标记图标(目前为 0;-)。
    • 不要忘记PyUnicode_AsWideCharString 或类似的东西,将返回的对象从PyObject_Str 转换为有用的C 字符串。
    【解决方案4】:

    这个线程对我来说非常有用,但是当我尝试在没有回溯的情况下提取错误消息本身时,我遇到了 Python C API 的问题。我在 Python 中找到了很多方法来做到这一点,但在 C++ 中我找不到任何方法来做到这一点。我终于想出了以下版本,它尽可能少地使用 C API,而更多地依赖于 boost python。

    PyErr_Print();
    
    using namespace boost::python;
    
    exec("import traceback, sys", mainNamespace_);
    auto pyErr = eval("str(sys.last_value)", mainNamespace_);
    auto pyStackTrace = eval("'\\n'.join(traceback.format_exception(sys.last_type, sys.last_value, sys.last_traceback))", mainNamespace_);
    
    stackTraceString_ = extract<std::string>(pyStackTrace);
    errorSummary_ = extract<std::string>(pyErr);
    

    之所以有效,是因为PyErr_Print() 还设置了sys.last_valuesys.last_typesys.last_traceback 的值。这些设置为与 sys.exc_info 给出的相同值,因此这在功能上类似于以下 python 代码:

    import traceback
    import sys
    
    try:
        raise RuntimeError("This is a test")
    except:
        err_type = sys.exc_info()[0]
        value = sys.exc_info()[1]
        tb = sys.exc_info()[2]
    
        stack_trace = "\n".join(traceback.format_exception(err_type, value, tb))
        error_summary = str(value)
    
    
    print(stack_trace)
    print(error_summary)
    

    我希望有人觉得这很有用!

    【讨论】:

    【解决方案5】:

    这里有一些基于其他答案和 cmets 的代码,用现代 C++ 和 cmets 很好地格式化。最低限度的测试,但它似乎工作。

    #include <string>
    #include <boost/python.hpp>
    #include <Python.h>
    
    // Return the current Python error and backtrace as a string, or throw
    // an exception if there was none.
    std::string python_error_string() {
      using namespace boost::python;
    
      PyObject* ptype = nullptr;
      PyObject* pvalue = nullptr;
      PyObject* ptraceback = nullptr;
    
      // Fetch the exception information. If there was no error ptype will be set
      // to null. The other two values might set to null anyway.
      PyErr_Fetch(&ptype, &pvalue, &ptraceback);
      if (ptype == nullptr) {
        throw std::runtime_error("A Python error was detected but when we called "
                                 "PyErr_Fetch() it returned null indicating that "
                                 "there was no error.");
      }
    
      // Sometimes pvalue is not an instance of ptype. This converts it. It's
      // done lazily for performance reasons.
      PyErr_NormalizeException(&ptype, &pvalue, &ptraceback);
      if (ptraceback != nullptr) {
        PyException_SetTraceback(pvalue, ptraceback);
      }
    
      // Get Boost handles to the Python objects so we get an easier API.
      handle<> htype(ptype);
      handle<> hvalue(allow_null(pvalue));
      handle<> htraceback(allow_null(ptraceback));
    
      // Import the `traceback` module and use it to format the exception.
      object traceback = import("traceback");
      object format_exception = traceback.attr("format_exception");
      object formatted_list = format_exception(htype, hvalue, htraceback);
      object formatted = str("\n").join(formatted_list);
      return extract<std::string>(formatted);
    }
    

    顺便说一句,我很好奇为什么每个人都使用handle&lt;&gt; 而不是handle。显然它禁用了模板参数推导。不知道你为什么要在这里,但无论如何都不一样,而且 Boost 文档说也要使用 handle&lt;&gt;,所以我想这是有充分理由的。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-12-13
      • 2011-05-17
      • 2013-08-13
      • 2010-10-19
      • 2022-11-24
      相关资源
      最近更新 更多