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

找到了一个方便的库来做我需要的: 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"

使用SimpleNamespace可以在顶层工作,但不会转换嵌套结构。

dct = yaml.safe_load(...)
obj = types.SimpleNamespace(**dct)

要实现完整的对象树转换,请执行以下操作:

def load_object(dct):
return types.SimpleNamespace(**dct)
dct = yaml.safe_load(...)
obj = json.loads(json.dumps(dct), object_hook=load_object)

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

自从dictPyYAML 使用是硬编码的,无法修补,您不仅需要提供 一个类似字典的类,可以随心所欲地运行,您还必须通过箍来制作 PyYAML使用该类。即更改通常构造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

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

如果使用标记注释 YAML 文件的根节点,则可以定义派生自YAMLObject的 Python 类来处理此问题,如 PyYAML 文档中所述。

但是,如果您希望 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]
"""))

但是,此方法仅在 YAML 结构均匀时有效。您通过在第一项的b列表中(qrs(中使用不同名称的字段来提供异构结构;xyz在第二项中(。我将 YAML 输入更改为具有相同的字段名称,因为对于不同的字段,此方法不起作用。我不确定您的 YAML 是否实际上是异构的,或者您只是不小心这样做了。如果您的 YAML 实际上是异构的,则通过字典访问访问项目是唯一可行的方法,因为从那时起,YAML 文件中的键与类字段不对应;它们是动态映射条目。

可以使用蒙克的munchify。

import yaml
from munch import munchify
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]
""")
config = munchify(mydict)
print(config.b[0].q) #foo
print(config.c.f[2]) #11

最新更新