首页 > 解决方案 > 如何在python中解析深度嵌套的yaml数据结构

问题描述

我们有一个 YAML 文件,看起来像下面这样:

all:
  children:
    allnetxsites:
      children:
        netxsites:
          hosts:
            bar.:
              ansible_ssh_host: bart.j
              domain: bart.local.domain
              nfs: lars.local.domain

我将如何获取bar.key 的 value 和 value nfs

蟒蛇代码:

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

标签: pythonyaml

解决方案


您的标题使您看起来对正在发生的事情或至少对术语感到有些困惑:尽管“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 的一个子集并且加载的更少,它无法处理input.yaml.

上面提到的片段,@aaaaa 正在使用的片段,由于(直接)嵌套的序列/列表,将在加载的 YAML 上中断


推荐阅读