【问题标题】:Load YAML as nested objects instead of dictionary in Python将 YAML 加载为嵌套对象而不是 Python 中的字典
【发布时间】:2019-03-05 08:50:40
【问题描述】:

我在 YAML 中有一个配置文件,当前使用 yaml.safe_load 作为字典加载。为了方便编写代码,我更愿意将其加载为一组嵌套对象。引用字典的更深层次很麻烦,并且使代码更难阅读。

例子:

import yaml
mydict = yaml.safe_load("""
a: 1
b:
- q: "foo"
  r: 99
  s: 98
- x: "bar"
  y: 97
  z: 96
c:
  d: 7
  e: 8
  f: [9,10,11]
""")

目前,我访问类似的项目

mydict["b"][0]["r"]
>>> 99

我希望能够访问相同的信息,例如

mydict.b[0].r
>>> 99

有没有办法像这样将 YAML 加载为嵌套对象?还是我必须滚动自己的类并将这些字典递归地翻转为嵌套对象?我猜 namedtuple 可以让这更容易一些,但我更喜欢现成的解决方案。

【问题讨论】:

  • @roganjosh 你能否证实你的说法,即没有办法做到这一点。

标签: python yaml


【解决方案1】:

找到了一个方便的库来满足我的需要: https://github.com/Infinidat/munch

import yaml
from munch import Munch
mydict = yaml.safe_load("""
a: 1
b:
- q: "foo"
  r: 99
  s: 98
- x: "bar"
  y: 97
  z: 96
c:
  d: 7
  e: 8
  f: [9,10,11]
""")
mymunch = Munch(mydict)

(我必须编写一个简单的方法来递归地将所有 subdicts 转换为 munches,但现在我可以使用例如导航我的数据

>>> mymunch.b.q
"foo"

【讨论】:

  • 我相信它应该是munch.munchify(mydict),所以它也递归地为嵌套字典创建“Munch”对象。
【解决方案2】:

像这样使用 SimpleNamespace 是什么样的作品:

import yaml
import json
from types import SimpleNamespace

dict = yaml.safe_load(definition)
obj = SimpleNamespace(**dict)

唯一的问题是它不支持嵌套/递归字典。 为了实现完整的对象树转换,我使用:

dict = yaml.safe_load(definition)
obj = json.loads(json.dumps(dict), object_hook=lambda d: SimpleNamespace(**d))

【讨论】:

    【解决方案3】:

    这可以相对容易地完成,并且无需更改输入文件。

    自从 dict PyYAML 使用是硬编码的,不能打补丁,你不仅要提供 一个行为如你所愿的类 dict 类,你还必须通过箍来制作 PyYAML 使用该类。 IE。更改通常会构造 dictSafeConstructor 要使用该新类,请将其合并到新的加载器中并使用 PyYAML 的 load 来使用该加载器:

    import sys
    import yaml
    
    from yaml.loader import Reader, Scanner, Parser, Composer, SafeConstructor, Resolver
    
    class MyDict(dict):
       def __getattr__(self, name):
           return self[name]
    
    class MySafeConstructor(SafeConstructor):
       def construct_yaml_map(self, node):
           data = MyDict()
           yield data
           value = self.construct_mapping(node)
           data.update(value)
    
    MySafeConstructor.add_constructor(
      u'tag:yaml.org,2002:map', MySafeConstructor.construct_yaml_map)
    
    
    class MySafeLoader(Reader, Scanner, Parser, Composer, MySafeConstructor, Resolver):
        def __init__(self, stream):
            Reader.__init__(self, stream)
            Scanner.__init__(self)
            Parser.__init__(self)
            Composer.__init__(self)
            MySafeConstructor.__init__(self)
            Resolver.__init__(self)
    
    
    yaml_str = """\
    a: 1
    b:
    - q: "foo"
      r: 99
      s: 98
    - x: "bar"
      y: 97
      z: 96
    c:
      d: 7
      e: 8
      f: [9,10,11]
    """
    
    mydict = yaml.load(yaml_str, Loader=MySafeLoader)
    
    print(mydict.b[0].r)
    

    给出:

    99
    

    如果您需要能够处理 YAML1.2,您应该使用 ruamel.yaml (免责声明:我是该软件包的作者)这使得上述内容稍微简单

    import ruamel.yaml
    
    # same definitions for yaml_str, MyDict
    
    class MySafeConstructor(ruamel.yaml.constructor.SafeConstructor):
       def construct_yaml_map(self, node):
           data = MyDict()
           yield data
           value = self.construct_mapping(node)
           data.update(value)
    
    MySafeConstructor.add_constructor(
      u'tag:yaml.org,2002:map', MySafeConstructor.construct_yaml_map)
    
    
    yaml = ruamel.yaml.YAML(typ='safe')
    yaml.Constructor = MySafeConstructor
    mydict = yaml.load(yaml_str)
    
    print(mydict.b[0].r)
    

    这也给出了:

    99
    

    (如果您的实际输入很大,加载数据的速度应该会明显更快)

    【讨论】:

      【解决方案4】:

      如果你用标签来注释 YAML 文件的根节点,你可以定义派生自 YAMLObject 的 Python 类来处理这个 as described in the PyYAML documentation

      但是,如果您希望 YAML 与标签保持清洁,您可以自己构建嵌套类(取自 my answer to a similar question):

      import yaml
      
      class BItem:
          def __init__(self, q, r, s):
              self.q, self.r, self.s = q, r, s
      
      class CItem:
          def __init__(self, raw):
              self.d, self.e, self.f = raw['d'], raw['e'], raw['f']
      
      class Root:
          def __init__(self, raw):
              self.a = raw['a']
              self.b = [BItem(i['q'], i['r'], i['s']) for i in raw['b']]
              self.c = CItem(raw['c'])
      
      mydict = Root(yaml.safe_load("""
      a: 1
      b:
      - q: "foo"
        r: 99
        s: 98
      - q: "bar"
        r: 97
        s: 96
      c:
        d: 7
        e: 8
        f: [9,10,11]
      """))
      

      但是,这种方法仅适用于您的 YAML 结构同质化的情况。您通过在b 列表中具有不同命名的字段(第一项中的qrs;第二项中的xyz)提供了一个异构结构.我将 YAML 输入更改为具有相同的字段名称,因为对于不同的字段,这种方法不起作用。我不确定您的 YAML 是否实际上是异构的,或者您只是不小心将其作为示例。如果您的 YAML 实际上是异构的,那么从那时起,通过 dict 访问访问项目是唯一可行的方法,YAML 文件中的键与类字段不对应;它们是动态映射条目。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2012-09-10
        • 1970-01-01
        • 2019-08-05
        • 2019-02-11
        • 1970-01-01
        • 2015-11-05
        • 1970-01-01
        • 2015-08-08
        相关资源
        最近更新 更多