Python:使用“点表示法”访问 YAML 值

新手上路,请多包涵

我正在使用 YAML 配置文件。所以这是在 Python 中加载我的配置的代码:

 import os
import yaml
with open('./config.yml') as file:
    config = yaml.safe_load(file)

这段代码实际上创建了一个字典。现在的问题是,为了访问我需要使用大量括号的值。

山药:

 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)

那会是一个“好的”解决方案吗?如果没有,什么是更好的选择?

附加问题[已解决]

我决定使用 Ilja Everilä 的解决方案。但现在我有一个额外的问题:您将如何围绕 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))

更好的解决方案(Ilja Everilä)

 super().__init__(yaml.safe_load(file))

原文由 Lugaxx 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 753
2 个回答

简单的

您可以使用 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 解析器和工具,例如在 这个答案 中。


令人费解的

轻松一点(不要太认真),您可以创建一个允许使用属性访问的包装器:

 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'

原文由 Ilja Everilä 发布,翻译遵循 CC BY-SA 3.0 许可协议

我最终使用了 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 中提供了更多示例。

原文由 evolved 发布,翻译遵循 CC BY-SA 4.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题