使用Boost::Python在C++应用程序中嵌入Python:第二部分

翻译: Leon Lee([email protected])
原文:在此

在第1部分中,我们了解了如何在C++应用程序中嵌入Python,包括从应用程序调用Python代码的几种方法。虽然我之前承诺在第2部分中完整实现一个配置解析器,但我认为看一下错误解析会更有建设性。一旦我们有一个很好的方法来处理Python代码中的错误,我将在第3部分中创建承诺的配置解析器。我们开始吧!

如果您获得了本教程的git repo副本并且正在使用它,您可能已经体验过boost::python处理Python错误的方式-- error_already_set异常类型。如果没有,以下代码将生成异常:

namespace py = boost::python;
...
Py_Initialize();
...
py::object rand_mod = py::import("fake_module");

…它的输出不是那么有用:

terminate called after throwing an instance of 'boost::python::error_already_set' 
Aborted

简而言之,boost::python处理的Python代码中发生的任何错误都会导致库抛出此异常; 遗憾的是,该异常并未封装有关错误本身的任何信息。要提取有关错误的信息,我们将不得不求助于使用Python C API和一些Python本身的机制。首先,捕捉错误:

try{
    Py_Initialize();
    py::object rand_mod = py::import("fake_module");
}catch(boost::python::error_already_set const &){
    std::string perror_str = parse_python_exception();
    std::cout << "Error in Python: " << perror_str << std::endl;
}

这里,我们调用parse_python_exception函数来提取错误字符串并将其打印出来。如此所示,异常数据静态存储在Python库中,而不是封装在异常本身中。parse_python_exception函数的第一步是使用Python C API的PyErr_Fetch函数提取该数据:

std::string parse_python_exception(){
    PyObject *type_ptr = NULL, *value_ptr = NULL, *traceback_ptr = NULL;
    PyErr_Fetch(&type_ptr, &value_ptr, &traceback_ptr);
    std::string ret("Unfetchable Python error");
...

由于可能存在全部、部分或没有异常数据,我们使用回退值设置返回的字符串。接下来,我们尝试从异常信息中提取和字符串化类型数据:

...
if(type_ptr != NULL){
    py::handle<> h_type(type_ptr);
    py::str type_pstr(h_type);
    py::extract e_type_pstr(type_pstr);
    if(e_type_pstr.check())
        ret = e_type_pstr();
    else
        ret = "Unknown exception type";
}
...

在这个块中,我们首先检查是否真有一个指向类型数据的有效指针。如果存在,我们构造一个boost::python::handle指向该数据,然后我们从中创建一个str对象。此转换应确保可以进行有效的字符串提取,但要进行双重检查,我们创建一个提取对象,检查对象,然后在有效的情况下执行提取。否则,我们使用回退字符串作为类型信息。

接着,我们对异常值执行非常类似的步骤:

...
if(value_ptr != NULL){
    py::handle<> h_val(value_ptr);
    py::str a(h_val);
    py::extract returned(a);
    if(returned.check())
        ret +=  ": " + returned();
    else
        ret += std::string(": Unparseable Python error: ");
}
...

我们将值字符串附加到现有错误字符串。对于大多数内置异常类型,值字符串是描述错误的可读字符串。

最后,我们提取回溯数据:

    if(traceback_ptr != NULL){
        py::handle<> h_tb(traceback_ptr);
        py::object tb(py::import("traceback"));
        py::object fmt_tb(tb.attr("format_tb"));
        py::object tb_list(fmt_tb(h_tb));
        py::object tb_str(py::str("\n").join(tb_list));
        py::extract returned(tb_str);
        if(returned.check())
            ret += ": " + returned();
        else
            ret += std::string(": Unparseable Python traceback");
    }
    return ret;
}

回溯类似于类型和值提取,除了将回溯对象格式化为字符串的额外步骤。为此,我们导入traceback模块。从traceback中,我们然后提取format_tb函数并使用traceback对象的句柄调用它。这会生成一个回溯字符串列表,然后我们将它们连接成一个字符串。也许不是最漂亮的输出,但它完成了工作。最后,我们如上所述提取C ++字符串类型,并将其附加到返回的错误字符串并返回整个结果。

在前面错误的上下文中,应用程序现在生成以下输出:

Error in Python: : No module named fake_module

As I mentioned above, in Part 3 I will walk through the implementation of a configuration parser built on top of the ConfigParser Python module. Assuming, of course, that I don't get waylaid again.

一般来说,这个函数可以更容易地找到嵌入Python代码中问题的根本原因。需要注意的是:如果您正在为嵌入解释器配置自定义Python环境(尤其是模块路径),则该parse_python_exception函数本身可能在尝试加载traceback模块时抛出一个boost::error_already_set异常,因此您可能希望将对函数的调用包装到try...catch块中并解析结果中的类型和值指针。

如上所述,在第3部分中,我将介绍构建在ConfigParserPython模块之上的配置解析器的实现。当然,假设我没有再次中断。

你可能感兴趣的:(编程)