我正在使用YAML配置文件。所以这是在Python中加载我的配置的代码:
import os
import yaml
with open('./config.yml') as file:
config = yaml.safe_load(file)
此代码实际上创建了一个字典。现在的问题是,为了访问值,我需要使用大量的括号。
YAML:
mysql:
user:
pass: secret
的Python:
import os
import yaml
with open('./config.yml') as file:
config = yaml.safe_load(file)
print(config['mysql']['user']['pass']) # <--
我更喜欢那样的(点符号):
config('mysql.user.pass')
所以,我的想法是利用PyStache render()接口。
import os
import yaml
with open('./config.yml') as file:
config = yaml.safe_load(file)
import pystache
def get_config_value( yml_path, config ):
return pystache.render('{{' + yml_path + '}}', config)
get_config_value('mysql.user.pass', config)
这会是一个“好”的解决方案吗?如果没有,那么什么是更好的选择呢?
其他问题[已解决]
我决定使用IljaEverilä的解决方案。但现在我还有一个问题:你如何在DotConf周围创建一个包装器Config类?
以下代码不起作用,但我希望您能了解我正在尝试做的事情:
class Config( DotDict ):
def __init__( self ):
with open('./config.yml') as file:
DotDict.__init__(yaml.safe_load(file))
config = Config()
print(config.django.admin.user)
错误:
AttributeError: 'super' object has no attribute '__getattr__'
解决方案
您只需要将self
传递给超类的构造函数。
DotDict.__init__(self, yaml.safe_load(file))
更好的解决方案(IljaEverilä)
super().__init__(yaml.safe_load(file))
答案 0 :(得分:14)
您可以使用reduce
从配置中提取值:
In [41]: config = {'asdf': {'asdf': {'qwer': 1}}}
In [42]: from functools import reduce
...:
...: def get_config_value(key, cfg):
...: return reduce(lambda c, k: c[k], key.split('.'), cfg)
...:
In [43]: get_config_value('asdf.asdf.qwer', config)
Out[43]: 1
如果你的YAML使用非常有限的语言子集,这个解决方案很容易维护并且很少有新的边缘情况。
使用正确的YAML解析器和工具,例如this answer。
在较轻松的注释上(不要太认真),您可以创建一个允许使用属性访问的包装器:
In [47]: class DotConfig:
...:
...: def __init__(self, cfg):
...: self._cfg = cfg
...: def __getattr__(self, k):
...: v = self._cfg[k]
...: if isinstance(v, dict):
...: return DotConfig(v)
...: return v
...:
In [48]: DotConfig(config).asdf.asdf.qwer
Out[48]: 1
请注意,关键字失败,例如“as”,“pass”,“if”等。
最后,你可能会变得非常疯狂(阅读:可能不是一个好主意)并自定义dict
以处理虚线字符串和元组键作为一种特殊情况,具有属性访问混合中抛出的项目(使用它限制):
In [58]: class DotDict(dict):
...:
...: # update, __setitem__ etc. omitted, but required if
...: # one tries to set items using dot notation. Essentially
...: # this is a read-only view.
...:
...: def __getattr__(self, k):
...: try:
...: v = self[k]
...: except KeyError:
...: return super().__getattr__(k)
...: if isinstance(v, dict):
...: return DotDict(v)
...: return v
...:
...: def __getitem__(self, k):
...: if isinstance(k, str) and '.' in k:
...: k = k.split('.')
...: if isinstance(k, (list, tuple)):
...: return reduce(lambda d, kk: d[kk], k, self)
...: return super().__getitem__(k)
...:
...: def get(self, k, default=None):
...: if isinstance(k, str) and '.' in k:
...: try:
...: return self[k]
...: except KeyError:
...: return default
...: return super().get(k, default=default)
...:
In [59]: dotconf = DotDict(config)
In [60]: dotconf['asdf.asdf.qwer']
Out[60]: 1
In [61]: dotconf['asdf', 'asdf', 'qwer']
Out[61]: 1
In [62]: dotconf.asdf.asdf.qwer
Out[62]: 1
In [63]: dotconf.get('asdf.asdf.qwer')
Out[63]: 1
In [64]: dotconf.get('asdf.asdf.asdf')
In [65]: dotconf.get('asdf.asdf.asdf', 'Nope')
Out[65]: 'Nope'
答案 1 :(得分:2)
一方面,您的示例使用get_config_value('mysql.user.pass', config)
采用正确的方法,而不是使用属性解决虚线访问。我不确定
如果你故意意识到你并没有尝试做更直观的事情:
print(config.mysql.user.pass)
,即使在重载__getattr__
时也无法开始工作,因为pass
是Python语言元素。
但是,您的示例仅描述了非常有限的YAML文件子集,因为它不涉及任何序列集合,也不涉及任何复杂的键。
如果你想要涵盖的不仅仅是微小的子集,例如扩展ruamel.yaml
:¹
def mapping_string_access(self, s, delimiter=None, key_delim=None):
def p(v):
try:
v = int(v)
except:
pass
return v
# possible extend for primitives like float, datetime, booleans, etc.
if delimiter is None:
delimiter = '.'
if key_delim is None:
key_delim = ','
try:
key, rest = s.split(delimiter, 1)
except ValueError:
key, rest = s, None
if key_delim in key:
key = tuple((p(key) for key in key.split(key_delim)))
else:
key = p(key)
if rest is None:
return self[key]
return self[key].string_access(rest, delimiter, key_delim)
ruamel.yaml.comments.CommentedMap.string_access = mapping_string_access
def sequence_string_access(self, s, delimiter=None, key_delim=None):
if delimiter is None:
delimiter = '.'
try:
key, rest = s.split(delimiter, 1)
except ValueError:
key, rest = s, None
key = int(key)
if rest is None:
return self[key]
return self[key].string_access(rest, delimiter, key_delim)
ruamel.yaml.comments.CommentedSeq.string_access = sequence_string_access
设置完成后,您可以执行以下操作:
yaml_str = """\
mysql:
user:
pass: secret
list: [a: 1, b: 2, c: 3]
[2016, 9, 14]: some date
42: some answer
"""
config = ruamel.yaml.round_trip_load(yaml_str)
def get_config_value(path, data, **kw):
return data.string_access(path, **kw)
print(get_config_value('mysql.user.pass', config))
print(get_config_value('mysql:user:pass', config, delimiter=":"))
print(get_config_value('mysql.list.1.b', config))
print(get_config_value('mysql.2016,9,14', config))
print(config.string_access('mysql.42'))
,并提供:
secret
secret
2
some date
some answer
表明,通过更多的预见和非常少的额外工作,您可以灵活地对许多YAML文件进行多点访问,而不仅仅是将字符串标量作为键的递归映射。
config.string_access(
mysql.user.pass )
,而不是定义和使用get_config_value()
¹这是使用ruamel.yaml YAML 1.2解析器完成的,我是作者。
答案 2 :(得分:1)
前一段时间我遇到了同样的问题并建立了这个吸气剂:
def get(self, key):
"""Tries to find the configuration value for a given key.
:param str key: Key in dot-notation (e.g. 'foo.lol').
:return: The configuration value. None if no value was found.
"""
try:
return self.__lookup(self.config, key)
except KeyError:
return None
def __lookup(self, dct, key):
"""Checks dct recursive to find the value for key.
Is used by get() interanlly.
:param dict dct: The configuration dict.
:param str key: The key we are looking for.
:return: The configuration value.
:raise KeyError: If the given key is not in the configuration dict.
"""
if '.' in key:
key, node = key.split('.', 1)
return self.__lookup(dct[key], node)
else:
return dct[key]
getter以递归方式从self.config
查找配置值(使用__lookup
)。
如果您在调整此案例时遇到问题,请随时寻求进一步的帮助。
答案 3 :(得分:1)
我最终使用了python-box。
该软件包提供了多种读取配置文件的方式(yaml,csv,json等)。
不仅如此,它还允许您直接传递dict
或字符串:
from box import Box
import yaml # Only required for different loaders
# Pass dict directly
movie_box = Box({ "Robin Hood: Men in Tights": { "imdb stars": 6.7, "length": 104 } })
# Load from yaml file
# Here it is also possible to use PyYAML arguments,
# for example to specify different loaders e.g. SafeLoader or FullLoader
conf = Box.from_yaml(filename="./config.yaml", Loader=yaml.FullLoader)
conf.mysql.user.pass
Wiki中提供了更多示例。
答案 4 :(得分:0)
这是一个很老的问题,但是我来这里是为了寻找答案,但是正在寻找更简单的解决方案。最后,使用easydict
库提出了自己的解决方案;使用pip install easydict
def yaml_load(fileName):
import yaml
from easydict import EasyDict as edict
fc = None
with open(fileName, 'r') as f:
fc = edict(yaml.load(f))
## or use safe_load
## fc = edict(yaml.safe_load(f))
return fc
现在,只需使用有效的yaml_load
调用yaml filename
:
config = yaml_load('./config.yml')
## assuming: config["mysql"]["user"]["pass"] is a valid key in config.yml
print("{}".format(config.mysql.user.pass))
答案 5 :(得分:0)
我通常会遵循以下最佳做法:将配置(任何形式,不仅仅是yaml)转换为内存中对象。
通过这种方式,基于文本的配置被1个函数解包,并且文本被丢弃,从而提供了一个漂亮的对象,与使每个函数都可以处理配置。这样,所有功能只能知道一个内部对象接口。如果从配置文件中添加/重命名/删除了任何新参数,则唯一要更改的功能是加载器功能,该功能将配置加载到内存对象中。
下面是我将FloydHub配置yaml文件加载到内存对象中的示例。我觉得这是一个很好的设计模式。
首先定义一个配置代表类,如下所示:
find (\bank -> bank.id == searchId) banks
然后将Yaml 加载到对象中以供进一步使用:
class FloydYamlConfig(object):
class Input:
def __init__(self, destination, source):
self.destination = destination
self.source = source
def __init__(self, floyd_yaml_dict):
self.machine = floyd_yaml_dict['machine']
self.env = floyd_yaml_dict['env']
self.description = floyd_yaml_dict['description']
self.max_runtime = floyd_yaml_dict['max_runtime']
self.command = floyd_yaml_dict['command']
self.input = []
for input_conf in floyd_yaml_dict['input']:
input_obj = self.Input(destination=input_conf['destination'], source=input_conf['source'])
self.input.append(input_obj)
def __str__(self):
input_str = ''
for input_obj in self.input:
input_str += '\ndestination: {}\n source: {}'.format(input_obj.destination, input_obj.source)
print_str = ('machine: {}\n'
'env: {}\n'
'input: {}\n'
'description: {}\n'
'max_runtime: {}\n'
'command: {}\n').format(
self.machine, self.env, input_str, self.description, self.max_runtime, self.command)
return print_str
样品量
floyd_conf = read_floyd_yaml_config(args.floyd_yaml_path)
def read_floyd_yaml_config(floyd_yaml_path) -> FloydYamlConfig:
with open(floyd_yaml_path) as f:
yaml_conf_dict = yaml.safe_load(f)
floyd_conf = FloydYamlConfig(yaml_conf_dict)
# print(floyd_conf)
return floyd_conf