翻译: Leon Lee(liyi@pansafe.com)
原文:在此
在本教程的第2部分中,我介绍了一种方法,使用应用程序的C++代码处理嵌入的Python代码抛出的异常。这对于调试嵌入式Python代码至关重要。在本教程中,我们将创建一个简单的C++类,它利用Python功能来处理开发实际应用程序中经常令人烦恼的部分:配置解析。
为了不让C++精英们感到愤怒,我将以外交方式说出这一点:我在C++中使用复杂的字符串操作。STL strings
和stringstreams
极大简化了任务,但执行应用程序级任务,并以健壮的方式执行它们,总是导致我编写更多的代码。因此,我最近使用嵌入Python,特别是ConfigParser
模块,重新编写了Granola Connect(Granola Enterprise中用于处理与Granola REST API通信的守护进程)的配置解析机制。
当然,字符串操作和配置解析只是一个例子。对于第3部分,我可以选择任何数量的C++难以处理而Python中很简单的任务(例如,Web连接),但是配置解析类是一个简单但完整的用于嵌入Python以供实际使用的示例。从Github repo中获取本教程的代码。
首先,让我们创建一个涵盖非常基本的配置解析的类定义:读取和解析INI样式的文件,提取给定名称和节的字符串值,并为给定的节设置字符串值。这是类声明:
class ConfigParser{
private:
boost::python::object conf_parser_;
void init();
public:
ConfigParser();
bool parse_file(const std::string &filename);
std::string get(const std::string &attr,
const std::string §ion = "DEFAULT");
void set(const std::string &attr,
const std::string &value,
const std::string §ion = "DEFAULT");
};
该ConfigParser
模块提供的功能远远超出本教程所涵盖的功能,但我们在此实现的子集应作为实现更复杂功能的模板。该类的实现相当简单; 首先,构造函数加载__main__
模块,提取字典,将ConfigParser
模块导入命名空间,并创建一个boost::python::object
类型的成员变量来包含RawConfigParser
对象:
ConfigParser::ConfigParser(){
py::object mm = py::import("__main__");
py::object mn = mm.attr("__dict__");
py::exec("import ConfigParser", mn);
conf_parser_ = py::eval("ConfigParser.RawConfigParser()", mn);
}
用以下config_parser_
对象执行文件解析以及值的获取和设置:
bool ConfigParser::parse_file(const std::string &filename){
return py::len(conf_parser_.attr("read")(filename)) == 1;
}
std::string ConfigParser::get(const std::string &attr, const std::string §ion){
return py::extract<std::string>(conf_parser_.attr("get")(section, attr));
}
void ConfigParser::set(const std::string &attr, const std::string &value, const std::string §ion){
conf_parser_.attr("set")(section, attr, value);
}
在这个简单的例子中,为了简洁起见,允许传播异常。在更复杂的环境中,您几乎肯定希望让C++类处理并将Python异常重新打包为C++异常。如果性能或其他问题成为问题,您可以稍后创建一个纯C++类。
要使用该类,调用代码可以简单地将其视为普通的C++类:
int main(){
Py_Initialize();
try{
ConfigParser parser;
parser.parse_file("conf_file.1.conf");
cout << "Directory (file 1): " << parser.get("Directory", "DEFAULT") << endl;
parser.parse_file("conf_file.2.conf");
cout << "Directory (file 2): " << parser.get("Directory", "DEFAULT") << endl;
cout << "Username: " << parser.get("Username", "Auth") << endl;
cout << "Password: " << parser.get("Password", "Auth") << endl;
parser.set("Directory", "values can be arbitrary strings", "DEFAULT");
cout << "Directory (force set by application): " << parser.get("Directory") << endl;
// Will raise a NoOption exception
// cout << "Proxy host: " << parser.get("ProxyHost", "Network") << endl;
}catch(boost::python::error_already_set const &){
string perror_str = parse_python_exception();
cout << "Error during configuration parsing: " << perror_str << endl;
}
}
就是这样:一个包含块和注释的键值配置解析器只需要50行代码。这只是冰山一角。在几乎相同长度的代码中,您可以执行各种各样的事情,这些事情在C++中最为痛苦,更容易出错且耗时:配置解析、列表和集合操作、Web连接、文件格式操作(想想XML/JSON),以及无数其他已在Python标准库中实现的任务。
在第4部分中,我将介绍如何使用仿函数和Python命名空间的类来更强大和通用地调用Python代码。