如何在Python中解析深度嵌套的YAML数据结构

5
我们有一个YAML文件,大致如下所示:
all:
  children:
    allnetxsites:
      children:
        netxsites:
          hosts:
            bar.:
              ansible_ssh_host: bart.j
              domain: bart.local.domain
              nfs: lars.local.domain

我该如何获取值bar.以及键nfs的值?

Python代码:

import yaml
with open("/Users/brendan_vandercar/sites.yaml", 'r') as stream:
    data_loaded = yaml.load(stream)

for element in data_loaded:
    name = "element"['all']['children']['allnetxsites']['children']['netxsites']['hosts']['bart']['nfs'][0]
    print(name)

我希望从这个脚本中获取一个列表输出,其具有以下特点:
Domain: bart.local.domain
NFS: lars.local.domain

相关:https://dev59.com/Imw05IYBdhLWcg3wTwKY - Esteis
2个回答

4

从你的标题可以看出,你对正在发生的事情或术语有些困惑:尽管“YAML数据结构”可能被理解为“从YAML文档加载的Python数据结构”,但你并没有进一步解析该数据结构。任何解析都是作为加载YAML文档的一部分完成的,并且在yaml.load()返回之前,解析已经完全结束。由于加载,你在Python中拥有一个数据结构,只需要通过递归遍历该数据结构来查找嵌套Python数据结构中的键。


你的YAML示例有些无趣,因为它只表示实际YAML的极小子集,因为你的YAML仅包含(普通的)标量字符串、映射和作为标量的映射键。

要遍历该数据结构,@aaaaaa提供的递归函数的简化版本即可:

import sys
import yaml

yaml_str = """\
all:
  children:
    allnetxsites:
      children:
        netxsites:
          hosts:
            bar.:
              ansible_ssh_host: bart.j
              domain: bart.local.domain
              nfs: lars.local.domain
"""

data = yaml.safe_load(yaml_str)

def find(key, dictionary):
    # everything is a dict
    for k, v in dictionary.items():
        if k == key:
            yield v
        elif isinstance(v, dict):
            for result in find(key, v):
                yield result

for x in find("nfs", data):
    print(x)

这将打印出预期的结果:

lars.local.domain

我已经简化了函数find,因为在代码片段中的列表处理是不正确的。

虽然使用的标量类型不影响递归查找,但您可能希望有一个更通用的解决方案,可以处理包含(嵌套)序列、标记节点和复杂映射键的YAML。

假设您的输入文件稍微复杂一些,为input.yaml

all:
  {a: x}: !xyz
  - [k, l, 0943]
  children:
    allnetxsites:
      children:
        netxsites:
          hosts:
            bar.:
              ansible_ssh_host: bart.j
              domain: bart.local.domain
              nfs: lars.local.domain

你可以使用ruamel.yaml(免责声明:我是该软件包的作者)来进行以下操作:

import sys
from pathlib import Path
import ruamel.yaml

in_file = Path('input.yaml')

yaml = ruamel.yaml.YAML()
data = yaml.load(in_file)

def lookup(sk, d, path=[]):
   # lookup the values for key(s) sk return as list the tuple (path to the value, value)
   if isinstance(d, dict):
       for k, v in d.items():
           if k == sk:
               yield (path + [k], v)
           for res in lookup(sk, v, path + [k]):
               yield res
   elif isinstance(d, list):
       for item in d:
           for res in lookup(sk, item, path + [item]):
               yield res

for path, value in lookup("nfs", data):
    print(path, '->', value)

这将会给出:

['all', 'children', 'allnetxsites', 'children', 'netxsites', 'hosts', 'bar.', 'nfs'] -> lars.local.domain

由于 PyYAML 仅解析 YAML 1.1 的某些子集,且加载的内容更少,因此无法处理中的有效 YAML。

@aaaaa 使用的上述代码片段将在加载的 YAML 上发生错误,因为它包含了(直接)嵌套的序列/列表。


1
也许这个片段可以为您提供一些帮助。
def find(key, dictionary):
    for k, v in dictionary.iteritems():
        if k == key:
            yield v
        elif isinstance(v, dict):
            for result in find(key, v):
                yield result
        elif isinstance(v, list):
            for d in v:
                for result in find(key, d):
                    yield result

那么你的代码等同于:
find('nfs', data_loaded)

1
.iteritems()只能在 Python 2 中使用,而该版本将于明年停止支持,请改用.items()。更严重的问题是在处理列表时的第二个类,这个“find”在直接嵌套列表上会出错,看起来列表是作为一个事后添加的,并且添加不正确。 - Anthon

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接