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

Posted

技术标签:

【中文标题】将 YAML 加载为嵌套对象而不是 Python 中的字典【英文标题】:Load YAML as nested objects instead of dictionary in 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 你能否证实你的说法,即没有办法做到这一点。 【参考方案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 文件中的键与类字段不对应;它们是动态映射条目。

【讨论】:

以上是关于将 YAML 加载为嵌套对象而不是 Python 中的字典的主要内容,如果未能解决你的问题,请参考以下文章

YAML - 转储没有类型/标签的嵌套对象

将 YAML 文件转换为 Python JSON 对象

Azure Devops:是不是可以将 yaml 模板嵌套在另一个 yaml 模板中?

yaml数组类型详解

python 将YAML映射加载为有序字典

yams-cpp 嵌套序列返回映射而不是值