您的位置:首页 > 编程语言 > Python开发

浅谈使用xml作为配置文件初始化自己的项目

2018-02-07 17:18 579 查看
当一个项目的代码不断增加,其中很多的内容诸如全局变量、提示语言等等都有必要放在一个独立的文件,方便变更。这个独立的文件有很多种,可以是init文件、conf文件、xml文件,为了通用性,我选择了xml文件作为自己的配置文件。对于《字符级的CNN文本分类器》一文中,我的xml文件是这样的:
<?xml version="1.0" encoding="UTF-8" ?>
<config>
<directories>
<directory name="train_text_home">TrainText</directory>
<directory name="test_text_home">TestText</directory>
<directory name="checkpoint_home">CheckPoints</directory>
<directory name="summary_home">Summaries</directory>
<directory name="log_home">Logs</directory>
</directories>
<files>
<file name="model_file">ENCharCNNTextClassification_%s</file>
<file name="summary_file">ENCharCNNTextClassification_%s</file>
<file name="log_file">Logs_of_%s.log</file>
</files>
<messages>
<module name="pre_train.py">
<message name="encode_success">One-Hot encoding done! Totally %d words have been skipped.</message>
<message name="start_train_file">Start to train from file: %s.</message>
<message name="done_train_file">Finish to train from file: %s.</message>
<message name="open_dir">Open directory: %s.</message>
</module>
<module name="char_cnn.py">
<message name="checkpoint_restore">Checkpoint: %s has been restored.</message>
<message name="checkpoint_restore_fail">No checkpoints being restored.</message>
<message name="display_steps">Total steps: %d, batch cost: %.4f, batch accuracy: %.2f%%, time to use: %d seconds.</message>
<message name="display_test">Accuracy: %.2f%%.</message>
</module>
<module name="main.py">
<message name="done_train">The train has been done!</message>
<message name="done_validation">The validation has been done!</message>
</module>
</messages>
<options>
<option name="train_name" type="str">ag_news</option>
<option name="max_to_keep" type="int">10</option>
<option name="epochs" type="int">55</option>
<option name="display_steps" type="int">2000</option>
<option name="save_steps" type="int">2000</option>
</options>
<hyperparameters>
<hyperparameter name="length0" type="int">1014</hyperparameter>
<hyperparameter name="n_class" type="int">4</hyperparameter>
<hyperparameter name="batch_s
11fb7
ize" type="int">128</hyperparameter>
<hyperparameter name="learning_rate" type="float">0.01</hyperparameter>
<hyperparameter name="decay_steps" type="int">1000</hyperparameter>
<hyperparameter name="decay_rate" type="float">0.8</hyperparameter>
<hyperparameter name="keep_prob" type="float">0.5</hyperparameter>
<hyperparameter name="grad_clip" type="int">5</hyperparameter>
</hyperparameters>
</config>xml是一种非常开放的语言,所有的标签、属性命名都没有相关的规定,什么时候使用子标签,什么时候使用属性全凭作者的习惯,这里我给出一种判断的标准:对于数据的存储,尽量使用子标签,例如上述中hyperparameter(超参)属于我要存的数据,所以我用标签来存。但name和type属于这个数据的一些属性,我使用属性去存储。但其实这样的分类法有时候并不会很清晰。例如电影,一部电影有标题、简介、演员等内容,这些属于这部电影的属性,但从另外一个角度来说,这些又是属于我们要存起来的数据。所以有另外一种标准,我们可以把需要直接使用的内容看成数据,例如这个超参直接赋给程序,电影的标题、简介和演员直接输出到前端页面,而name和type属于对这些数据的描述,我们不会直接使用它们,所以作为属性。在上述文件中,大家还可以看到我在部分内容里面使用了格式字符串“%s%d%f”等,关于这点的意义之一,我在另一篇文章中提到,就是为了可以存储整条文本,方便语言包的制作。具体怎样指定相关的变量,后面会介绍。
有了xml文件,就需要有代码去读,同样为了全局性考虑,我专门写了一个类来读取配置文件,做我程序的初始化工作:
import os
import logging
import xml.etree.ElementTree

class ProjectInitializer:
config = None
work_dir = os.getcwd()

@classmethod
def init_my_project(cls, config_file='config.xml'):
assert os.path.isfile(config_file)
assert (os.path.splitext(config_file)[-1]) == '.xml'
cls.config = xml.etree.ElementTree.parse(config_file).getroot()
cls._check_dirs()
cls._init_logging()

@classmethod
def get_dir_path(cls, home_name):
path = cls.config.find("./directories/directory[@name=\'%s\']" % home_name).text
if os.path.isabs(path):
return path
else:
return os.path.join(cls.work_dir, path)

@classmethod
def get_file_path(cls, home_name, file_name, *args):
real_file_name = cls.config.find("./files/file[@name=\'%s\']" % file_name).text
if len(args) > 0:
real_file_name = real_file_name % args
return os.path.join(cls.get_dir_path(home_name), real_file_name)

@classmethod
def message_about(cls, module_file_path, event_name, *args):
module_name = os.path.basename(module_file_path)
mess_module = cls.config.find("./messages/module[@name=\'%s\']" % module_name)
assert mess_module is not None
message = mess_module.find("./message[@name=\'%s\']" % event_name).text
if len(args) > 0:
message = message % args
return message

@classmethod
def option(cls, opt_name):
tag = cls.config.find("./options/option[@name=\'%s\']" % opt_name)
value = eval(tag.get('type'))(tag.text)
return value

@classmethod
def hyper_para(cls, para_name):
tag = cls.config.find("./hyperparameters/hyperparameter[@name=\'%s\']" % para_name)
value = eval(tag.get('type'))(tag.text)
return value

@classmethod
def _check_dirs(cls):
for directory in cls.config.findall("./directories/directory"):
path = cls.get_dir_path(directory.get('name'))
if not os.path.isdir(path):
os.mkdir(path)

@classmethod
def _init_logging(cls):
formatter = logging.Formatter('%(asctime)s %(name)s %(levelname)s - %(message)s', '%Y %b %d %H:%M:%S')

handlers = [
logging.FileHandler(cls.get_file_path('log_home', 'log_file', cls.option('train_name'))),
logging.StreamHandler()
]
logging.getLogger().setLevel(logging.INFO)
for handler in handlers:
handler.setFormatter(formatter)
logging.getLogger().addHandler(handler)且看我逐行解析:
class ProjectInitializer:
config = None
work_dir = os.getcwd()类变量config是存储xml解析树的,在import类的时候,因为配置文件的路径不确定,所以先初始化为None,而work_dir则是要获取当前的工作目录作为全局变量,这样的好处是,无论后面我们是否改变了工作目录,我们配置文件指定的路径都可以跟这个路径组合成绝对路径,固定存储的位置。
xml获取相关标签的公式如下:

20.5.2.2. Supported XPath syntax

SyntaxMeaning
tag
Selects all child elements with the given tag. For example, 
spam
 selects all child elements named 
spam
, and 
spam/egg
 selects all grandchildren named 
egg
 in all children named 
spam
.
*
Selects all child elements. For example, 
*/egg
 selects all grandchildren named 
egg
.
.
Selects the current node. This is mostly useful at the beginning of the path, to indicate that it’s a relative path.
//
Selects all subelements, on all levels beneath the current element. For example, 
.//egg
 selects all 
egg
 elements in the entire tree.
..
Selects the parent element. Returns 
None
 if the path attempts to reach the ancestors of the start element (the element 
find
was called on).
[@attrib]
Selects all elements that have the given attribute.
[@attrib='value']
Selects all elements for which the given attribute has the given value. The value cannot contain quotes.
[tag]
Selects all elements that have a child named 
tag
. Only immediate children are supported.
[tag='text']
Selects all elements that have a child named 
tag
 whose complete text content, including descendants, equals the given 
text
.
[position]
Selects all elements that are located at the given position. The position can be either an integer (1 is the first position), the expression 
last()
 (for the last position), or a position relative to the last position (e.g. 
last()-1
).
Predicates (expressions within square brackets) must be preceded by a tag name, an asterisk, or another predicate. 
position
 predicates must be preceded by a tag name.详细内容请参考xml.etree.ElementTree文档
@classmethod
def init_my_project(cls, config_file='config.xml'):
assert os.path.isfile(config_file)
assert (os.path.splitext(config_file)[-1]) == '.xml'
cls.config = xml.etree.ElementTree.parse(config_file).getroot()
cls._check_dirs()
cls._init_logging()第一个静态方法是用来初始化项目的,会在main函数的第一句被调用。从代码我们可以清楚看到,主要工作就是读取指定的xml文件,检查需要的目录是否存在,以及初始化logging的相关信息。
@classmethod
def get_dir_path(cls, home_name):
path = cls.config.find("./directories/directory[@name=\'%s\']" % home_name).text
if os.path.isabs(path):
return path
else:
return os.path.join(cls.work_dir, path)

@classmethod
def get_file_path(cls, home_name, file_name, *args):
real_file_name = cls.config.find("./files/file[@name=\'%s\']" % file_name).text
if len(args) > 0:
real_file_name = real_file_name % args
return os.path.join(cls.get_dir_path(home_name), real_file_name)这两个方法是用来获取相关的目录和文件路径的,为了便于在win和Linux之间迁移,我配置文件一般不会存路径分割符,而像代码中的使用os.path中的join方法来连接,这样就保证了可迁移性。同时在第二个方法中我们可以看到,函数使用了Python的一个可变参数的特性*args,这个特性允许使用着动态输入若干个参数,例如参数文件中的log文件我们会允许输入一个字符串来区分不同的日志,这样调用者获取log文件路径的时候,只需要把这个字符串按顺序放在函数里面,代码便会替换相应的内容,生成真正的文件名,再组合目录的路径成为绝对路径返回。由于这个参数的个数是可变的,也满足了字符串中替代符个数不定的需要。
@classmethod
def message_about(cls, module_file_path, event_name, *args):
module_name = os.path.basename(module_file_path)
mess_module = cls.config.find("./messages/module[@name=\'%s\']" % module_name)
assert mess_module is not None
message = mess_module.find("./message[@name=\'%s\']" % event_name).text
if len(args) > 0:
message = message % args
return message这个方法是用来获取提示信息的,由于提示信息非常多,而且一般每个模块有固定的提示信息,很少是各个模块共用的,所以在提示信息保存的xml里面,我增加了一个父标签<module>,标签的内容是这个文件的文件名。然后在调用函数的时候我们有一个取巧的方法:
(MyInit.message_about(__file__, 'done_train')__file__这个变量存储了当前模块的文件名,这样在不同的模块里面,我们便可以统一使用这样的代码来进行调用,减少了错误的发生:
MyInit.message_about(__file__, 'start_train_file', file_path)上面是带参数的调用。
@classmethod
def option(cls, opt_name):
tag = cls.config.find("./options/option[@name=\'%s\']" % opt_name)
value = eval(tag.get('type'))(tag.text)
return value

@classmethod
def hyper_para(cls, para_name):
tag = cls.config.find("./hyperparameters/hyperparameter[@name=\'%s\']" % para_name)
value = eval(tag.get('type'))(tag.text)
return value这两个都是获取全局变量的方法,其中option和hyperparameter并没有太严格的区分,我凭我个人感觉认为后者对神经网络的定义相关性更大。方法中值得一提的是,tag.text存的都是str变量,这样我们在使用的时候可能会面临变量类型的报错,所以我特意增加了一个type属性,然后使用Python的特殊方法eval执行,例如是int的变量,eval('int)('4')的效果就等同于int('4'),这样返回的全局变量变可以通过xml文件动态设置属性了。
@classmethod
def _check_dirs(cls):
for directory in cls.config.findall("./directories/directory"):
path = cls.get_dir_path(directory.get('name'))
if not os.path.isdir(path):
os.mkdir(path)该方法是检查相关的目录是否存在,没有就创建,代码比较简单。
@classmethod
def _init_logging(cls):
formatter = logging.Formatter('%(asctime)s %(name)s %(levelname)s - %(message)s', '%Y %b %d %H:%M:%S')

handlers = [
logging.FileHandler(cls.get_file_path('log_home', 'log_file', cls.option('train_name'))),
logging.StreamHandler()
]
logging.getLogger().setLevel(logging.INFO)
for handler in handlers:
handler.setFormatter(formatter)
logging.getLogger().addHandler(handler)最后一个方法是logging的初始化,由于本文主要讲的是xml读取,这部分代码只是简单介绍一下。formatter是用来定义格式的,handlers里面存了需要输出的地方,本代码里面是logfile和屏幕,把它设置到getLogger()获取的root logger里面,就可以对所有的log执行了。
上述类就是用来初始化我的代码的,我做人工智能的编程基本上都是照搬这两个文件,然后修改一下xml配置文件的内容,非常方便。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  xml Python 配置 初始化
相关文章推荐