在Python中,将YAML作为嵌套对象而不是字典加载

在Python中,将YAML作为嵌套对象而不是字典加载,python,yaml,Python,Yaml,我在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] """) 目前,我访问的项目包括 myd

我在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可以让这更容易一些,但我更喜欢一个现成的解决方案。

如果您用标记注释YAML文件的根节点,您可以定义从
YAMLObject
派生的Python类来处理这个问题

但是,如果希望YAML不受标记的影响,可以自己构造嵌套类(取自):


然而,只有当YAML的结构是均匀的时,这种方法才有效。通过在
b
列表中使用不同名称的字段(第一项中的
q
r
s
;第二项中的
x
y
z
)给出了异构结构。我将YAML输入更改为具有相同的字段名,因为对于不同的字段,这种方法不起作用。我不确定您的YAML是否实际上是异构的,或者您只是意外地将其作为一个示例。如果您的YAML实际上是异构的,那么通过dict访问访问条目是唯一可行的方法,因为YAML文件中的键与类字段不对应;它们是动态映射项。

这可以相对轻松地完成,而且无需更改输入文件

自从
dict
PyYAML使用的是硬编码的,无法修补,您不仅需要提供 一个像dict这样的类,如果你想表现的话,你还必须经历各种困难才能做到 Pyaml使用该类。即,更改通常会构造一个dict的
SafeConstructor
要使用该新类,请将其合并到新的加载程序中,并使用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

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

找到了一个方便的库,可以完全满足我的需要:

(我必须编写一个简单的方法来递归地将所有子部分转换为munches,但现在我可以使用

>>> mymunch.b.q
"foo"

使用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))

@roganjosh你能证明你的说法是没有办法做到这一点吗?我认为应该是
munch.munchify(mydict)
,所以它也会递归地为嵌套的dict创建“munch”对象。
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))