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

时间:2018-09-29 17:33:12

标签: 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]
""")

当前,我访问诸如

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

我想做的就是访问相同的信息

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

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

4 个答案:

答案 0 :(得分:1)

可以相对轻松地完成此操作,而无需更改输入文件。

dict PyYAML的使用是硬编码的,无法修补,您不仅需要提供 行为类似dict的类,您还需要遍历篮球 PyYAML使用该类。即更改通常会构成SafeConstructor的{​​{1}} 要使用该新类,请将其合并到新的Loader中,并使用PyYAML的dict来使用该Loader:

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)

如果您需要能够处理YAML1.2,则应使用ruamel.yaml (免责声明:我是该程序包的作者),这使上面的操作稍微简单了

99

这还提供:

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)

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

答案 1 :(得分:0)

如果使用标记注释YAML文件的根节点,则可以定义从def isRepeat(inputString): flag = False print(inputString)# printing this perfectly if len(inputString) % 2 !=0: return False else: for i in range(len(inputString)//2): print("x") if inputString.count(inputString[i]) %2 == 0: flag = True else: return False return flag output: inputString = "2w2ww" isRepeat(inputString) 2w2ww False 派生的Python类来处理此as described in the PyYAML documentation

但是,如果您希望自己的YAML保持标记的清洁,则可以自己构造嵌套类(摘自my answer to a similar question):

YAMLObject

但是,仅当您的YAML具有统一的结构时,此方法才有效。您通过在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] """)) bqr,第一项s,{{ 1}},第二项中的x。我将YAML输入更改为具有相同的字段名称,因为对于不同的字段,此方法不起作用。我不确定您的YAML是否实际上是异类的,或者您只是偶然地这样做而已,例如。如果您的YAML实际上是异构的,则从dict访问访问项目是唯一可行的方法,因为从那时起,YAML文件中的键并不对应于类字段;它们是动态映射条目。

答案 2 :(得分:0)

找到了一个方便的库来完全满足我的需要: 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)

((我不得不编写一种简单的方法来将所有下标递归转换为孟子,但是现在我可以使用

>>> mymunch.b.q
"foo"

答案 3 :(得分:0)

像这样使用 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))