将 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。更改通常会构造 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
(如果您的实际输入很大,加载数据的速度应该会明显更快)
【讨论】:
【参考方案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
列表中具有不同命名的字段(第一项中的q
、r
、s
;第二项中的x
、y
、z
)提供了一个异构结构.我将 YAML 输入更改为具有相同的字段名称,因为对于不同的字段,这种方法不起作用。我不确定您的 YAML 是否实际上是异构的,或者您只是不小心将其作为示例。如果您的 YAML 实际上是异构的,那么从那时起,通过 dict 访问访问项目是唯一可行的方法,YAML 文件中的键与类字段不对应;它们是动态映射条目。
【讨论】:
以上是关于将 YAML 加载为嵌套对象而不是 Python 中的字典的主要内容,如果未能解决你的问题,请参考以下文章