我正在尝试创建一个字典(从yaml数据中读取),表现得像一个类。因此,如果我要求class.key
,我会检索他的价值。代码如下:
import errno
import sys
import yaml
backup_conf="""
loglevel: INFO
username: root
password: globalsecret
destdir: /dsk/bckdir/
avoidprojects:
matchregex: /bkp/
depots:
server1:
password: asecret
server2:
username: root
server3:
server4:
destdir: /disk2/bkp/
projects:
proj1:
matchregex:
- /backups/
- /bkp/
"""
class Struct:
def __init__(self, **entries):
self.__dict__.update(entries)
class Config:
def __init__(self, filename="backup.cfg", data=None):
self.cfg = {}
if data is None:
try:
fd = open(filename,'r')
try:
yamlcfg = yaml.safe_load(fd)
except yaml.YAMLError as e:
sys.exit(e.errno)
finally:
fd.close()
except ( IOError, OSError ) as e:
sys.exit(e.errno)
else:
try:
yamlcfg = yaml.safe_load(data)
except yaml.YAMLError as e:
sys.exit(e.errno)
self.cfg = Struct(**yamlcfg)
def __getattribute__(self, name):
try:
return object.__getattribute__(self, name)
except AttributeError:
return self.cfg.__getattribute__(name)
def get_depot_param(self,depot,param):
try:
self.depot_param = self.cfg.depots[depot][param]
except ( TypeError, KeyError) as e:
try:
self.depot_param = getattr(self.cfg, param)
except KeyError as e:
sys.exit(e.errno)
return self.depot_param
def get_project_param(self,project,param):
try:
self.project_param = self.cfg.projects[project][param]
except ( TypeError, KeyError) as e:
try:
self.project_param = getattr(self.cfg, param)
except KeyError as e:
sys.exit(e.errno)
return self.project_param
def get_project_matches(self,project):
try:
self.reglist = self.cfg.projects[project]['matchregex']
except KeyError as e:
try:
self.reglist = self.cfg.matchregex
except KeyError as e:
print "Error in configuration file: {0}: No default regex defined. Please add a matchregex entry on conf file".format(e)
sys.exit(e.errno)
if isinstance(self.reglist, str):
self.reglist = self.reglist.split()
return self.reglist
def get_depots(self):
return self.cfg.depots.keys()
if __name__ == '__main__':
# Read config file to cfg
config = Config(data=backup_conf)
代码运行正常,我可以获取如下所示的数据:config.cfg.loglevel
按预期返回INFO
。但是我希望知道如何以config.loglevel
的方式调用cfg
清除来自self.cfg
实例变量的if(nodeTouched == ball){
runAction(SKAction.playSoundFileNamed("Tink", waitForCompletion: false))
}
。 (当然,欢迎任何增强代码的提示。)
答案 0 :(得分:2)
嗯,最简单的解决方案是使用PYYaml构造函数,即将类映射到yaml类型。
您所要做的就是让您的班级成为yaml.YAMLObject
的孩子,添加yaml_tag
成员告诉yaml何时使用该类构建该类的实例(而不是dict) ,并且您已经设置:
class Config(yaml.YAMLObject):
yaml_tag = '!Config'
@classmethod
def load(self, filename="backup.cfg", data=None):
self.cfg = {}
if data is None:
with open(filename,'r') as f:
yamlcfg = yaml.load(f)
else:
yamlcfg = yaml.load(data)
return yamlcfg
backup_conf="""
!Config
loglevel: INFO
username: root
password: globalsecret
destdir: /dsk/bckdir/
avoidprojects:
matchregex: /bkp/
depots:
server1:
password: asecret
server2:
username: root
server3:
server4:
destdir: /disk2/bkp/
projects:
proj1:
matchregex:
- /backups/
- /bkp/
"""
if __name__ == '__main__':
# Read config file to cfg
config = Config.load(data=backup_conf)
正如您所看到的,我使用工厂方法加载数据并创建实例,这就是load
类方法的用途。
该方法的一个优点是,您可以通过在yaml数据中编写类型标记来直接键入所有元素。因此,如果您需要,您也可以使用类似的方法键入您的服务器,使您的yaml像:
depots:
server1: !Server
password: asecret
server2: !Server
username: root
server3: !Server
server4: !Server
destdir: /disk2/bkp
与项目密钥中的每个项目相同。
namedtuple
s 如果您不想更改您的yaml,那么您可以使Config
类成为namedtuple
的孩子,当您加载yaml数据时,可以创建namedtuple
一个字典。
为此,在下面的代码片段中,我创建了一个遍历所有dict
s(和嵌套dict
s)的递归函数(嵌套在load类方法中)并将它们转换为namedtuple
s。
import yaml
from collections import namedtuple
class Config:
@classmethod
def load(self, filename='backup.cfg', data=None):
"""Load YAML document"""
def convert_to_namedtuple(d):
"""Convert a dict into a namedtuple"""
if not isinstance(d, dict):
raise ValueError("Can only convert dicts into namedtuple")
for k,v in d.iteritems():
if isinstance(v, dict):
d[k] = convert_to_namedtuple(v)
return namedtuple('ConfigDict', d.keys())(**d)
if data is None:
with open(filename, 'r') as f:
yamlcfg = yaml.load(f)
else:
yamlcfg = yaml.load(data)
return convert_to_namedtuple(yamlcfg)
当你运行它时:
>>> cfg = Config.load(data=backup_conf)
>>> print cfg.username, cfg.destdir
root /dsk/bckdir/
>>> print cfg.depots.server4.destdir
/disk2/bkp/
>>> print cfg.depots.server2.username
root
yaml.Loader
构建namedtuple
s 我试图找到一种方法来做到这一点,但经过一些尝试和错误之后,我明白这需要花费太多时间来弄明白,而且它太复杂了,因为它易于理解它是可行的解。 只是为了好玩,这就是难以实现的原因。
有一种方法可以创建自己的默认加载器,并更改默认节点的转换方式。在默认加载器中,您可以覆盖创建dict
的方法以使其创建namedtuple
s:
class ConfigLoader(yaml.Loader):
def construct_mapping(self, node, deep=False):
# do whatever it does per default to create a dict, i.e. call the ConfigLoader.construct_mapping() method
mapping = super(ConfigLoader, self).construct_mapping(node, deep)
# then convert the returned mapping into a namedtuple
return namedtuple('ConfigDict', mapping.keys())(**mapping)
唯一的问题是another method calling that one期望首先构建dict
树,然后才用值更新它:
def construct_yaml_map(self, node):
data = {}
yield data ## the object is returned here, /before/ it is being populated
value = self.construct_mapping(node)
data.update(value)
所以,正如我所说的那样,当然可以解决这个问题,但如果我花了太多时间去弄清楚,那么就没有必要告诉你如何去做,因为它会让你(以及未来的读者)难以理解。
在我看到@user1340544's answer时,您可能需要考虑使用EasyDict
代替collections.namedtuple
(如果您还好的话)
使用外部包裹。)
因此,您可以在此处看到data
字段构建为空字典,dict
yield
在调用者添加到调用者之前namedtuple
。因此,只有在构建dict之后才会添加值。
但是cut
需要在一个步骤中构建(即:您需要知道所有键之前),因此不能使用该方法。
我个人更喜欢选项①,使用标签,然后您可以使用它映射到的类来验证配置(并在丢失配置项或错误输入项或额外项时发出警报)。 您还可以通过为每种类型使用不同的名称来获得收益,从而可以轻松地在解析配置文件时报告错误,以及使用最少额外代码的所有内容。当然,选项②可以很好地完成工作。
HTH
答案 1 :(得分:0)
在将它们分配为属性后,不能轻易地迭代不同的映射键,您可以执行以下操作:
from __future__ import print_function
import errno
import sys
import yaml
backup_conf="""
loglevel: INFO
username: root
password: globalsecret
destdir: /dsk/bckdir/
avoidprojects:
matchregex: /bkp/
depots:
server1:
password: asecret
server2:
username: root
server3:
server4:
destdir: /disk2/bkp/
projects:
proj1:
matchregex:
- /backups/
- /bkp/
"""
class Struct:
pass
def __repr__(self):
res = {}
for x in dir(self):
if x.startswith('__'):
continue
res[x] = getattr(self, x)
return repr(res)
def assign_dict_as_attr(obj, d):
assert isinstance(d, dict)
for key in d:
value = d[key]
if isinstance(value, dict):
x = Struct()
setattr(obj, key, x)
assign_dict_as_attr(x, value)
else:
setattr(obj, key, value)
class Config:
def __init__(self, filename="backup.cfg", data=None):
self.cfg = {}
if data is None:
try:
fd = open(filename,'r')
try:
yamlcfg = yaml.safe_load(fd)
except yaml.YAMLError as e:
sys.exit(e.errno)
finally:
fd.close()
except ( IOError, OSError ) as e:
sys.exit(e.errno)
else:
try:
yamlcfg = yaml.safe_load(data)
except yaml.YAMLError as e:
sys.exit(e.errno)
print('yamlcfg', yamlcfg)
assign_dict_as_attr(self, yamlcfg)
if __name__ == '__main__':
# Read config file to cfg
config = Config(data=backup_conf)
print('loglevel', config.loglevel)
print('depots.server1', config.depots.server1)
print('depots.server1.password', config.depots.server1.password)
得到:
loglevel INFO
depots.server1 {'password': 'asecret'}
depots.server1.password asecret
另一种解决方案是让__getattr__()
更聪明:
class Struct:
def __init__(self, d):
self._cfg = d
def __getattr__(self, name):
res = self._cfg[name]
if isinstance(res, dict):
res = Struct(res)
return res
def __str__(self):
res = {}
for x in self._cfg:
if x.startswith('__'):
continue
res[x] = self._cfg[x]
return repr(res)
class Config:
def __init__(self, filename="backup.cfg", data=None):
self.cfg = {}
if data is None:
try:
fd = open(filename,'r')
try:
self._cfg = yaml.safe_load(fd)
except yaml.YAMLError as e:
sys.exit(e.errno)
finally:
fd.close()
except ( IOError, OSError ) as e:
sys.exit(e.errno)
else:
try:
self._cfg = yaml.safe_load(data)
except yaml.YAMLError as e:
sys.exit(e.errno)
def __getattr__(self, name):
res = self._cfg[name]
if isinstance(res, dict):
res = Struct(res)
return res
if __name__ == '__main__':
# Read config file to cfg
config = Config(data=backup_conf)
print('loglevel', config.loglevel)
print('depots.server1', config.depots.server1)
print('depots.server1.password', config.depots.server1.password)
它为您提供与以前相同的输出。
答案 2 :(得分:0)