在第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
模块之上的配置解析器的实现。当然,假设我没有再次中断。