PyYaml“包含文件”和 yaml 别名(锚点/引用)

Posted

技术标签:

【中文标题】PyYaml“包含文件”和 yaml 别名(锚点/引用)【英文标题】:PyYaml "include file" and yaml aliases (anchors/references) 【发布时间】:2017-12-08 05:12:06 【问题描述】:

我有一个大型 YAML 文件,其中大量使用了 YAML 锚点和引用,例如:

warehouse:
  obj1: &obj1
    key1: 1
    key2: 2
specific:
  spec1: 
    <<: *obj1
  spec2:
    <<: *obj1
    key1: 10

文件太大,所以我寻找了一种解决方案,可以让我拆分为 2 个文件:warehouse.yamlspecific.yaml,并将 warehouse.yaml 包含在 specific.yaml 中。我读了this simple article,它描述了我如何使用 PyYAML 来实现这一点,但它也说不支持合并键(

我真的出错了:

yaml.composer.ComposerError: found undefined alias 'obj1

当我尝试那样做的时候。

所以,我开始寻找替代方法,但我很困惑,因为我对 PyYAML 了解不多。

我可以获得所需的合并密钥支持吗?我的问题还有其他解决方案吗?

【问题讨论】:

我希望您知道,没有必要让锚点与对应键的字符串相同(obj1 【参考方案1】:

现在好像有人把这个问题作为ruamel.yaml的扩展解决了。

pip install ruamel.yaml.include(source on GitHub)

要得到上面想要的输出:

warehouse.yml

obj1: &obj1
  key1: 1
  key2: 2

specific.yml

specific:
  spec1: 
    <<: *obj1
  spec2:
    <<: *obj1
    key1: 10

您的代码将是:

from ccorp.ruamel.yaml.include import YAML

yaml = YAML(typ='safe', pure=True)
yaml.allow_duplicate_keys = True

with open('specific.yml', 'r') as ymlfile:
    return yaml.load(ymlfile)

如果您想在输出中包含仓库键,它还包括一个方便的 !exclude 功能。如果您只想要特定的密钥,您的 specific.yml 可以以:

!exclude includes:
- !include warehouse.yml

在这种情况下,您的warehouse.yml 还可以包含***warehouse: 键。

【讨论】:

Myfile.yaml: >> my_key1: - *obj1 - name: my_obj1 >>> pip install ccorp-yaml-include from ccorp.ruamel.yaml.include import YAML yaml = YAML(typ=' safe', pure=True) yaml.allow_duplicate_keys = True p = "C:\\Users\\yaml\\Myfile.yaml" f = open(p, 'r') yaml.load(f) >>> 错误: ruamel.yaml.composer.ComposerError:找到未定义的别名'obj1'【参考方案2】:

在 PyYAML 中处理锚点和别名的关键是字典 anchors,它是 Composer 的一部分。它将锚映射到节点,以便可以查找别名。它的存在受限于Composer 的存在,它是您使用的Loader 的复合元素。

Loader 类仅在调用 yaml.load() 期间存在,因此事后没有简单的方法可以提取它:首先,您必须使 Loader() 的实例持续存在,然后确保不调用普通的compose_document() 方法(其中包括self.anchors = ,以便为下一个文档(在单个流中)清理)。

如果你有warehouse.yaml,事情会更复杂:

warehouse:
  obj1: &obj1
    key1: 1
    key2: 2

specific.yaml:

warehouse: !include warehouse.yaml
specific:
  spec1:
    <<: *obj1
  spec2:
    <<: *obj1
    key1: 10

即使你可以保存、提取和传递锚信息,你也永远不会让它与你的 sn-p 一起工作,因为处理 specific.yaml 的作曲家会比标签 @987654338 更早遇到未定义的别名@ 用于构造(并填充anchors)。

您可以通过添加specific.yaml来规避此问题

specific:
  spec1:
    <<: *obj1
  spec2:
    <<: *obj1
    key1: 10

来自warehouse.yaml

warehouse:
  obj1: &obj1
    key1: 1
    key2: 2
specific: !include specific.yaml

,或将两者都包含在第三个文件中。 请注意,密钥 specific 在两个文件中

运行这两个文件:

import sys
from ruamel import yaml

def my_compose_document(self):
    self.get_event()
    node = self.compose_node(None, None)
    self.get_event()
    # self.anchors =     # <<<< commented out
    return node

yaml.SafeLoader.compose_document = my_compose_document

# adapted from http://code.activestate.com/recipes/577613-yaml-include-support/
def yaml_include(loader, node):
    with open(node.value) as inputfile:
        return list(my_safe_load(inputfile, master=loader).values())[0]
#              leave out the [0] if your include file drops the key ^^^

yaml.add_constructor("!include", yaml_include, Loader=yaml.SafeLoader)


def my_safe_load(stream, Loader=yaml.SafeLoader, master=None):
    loader = Loader(stream)
    if master is not None:
        loader.anchors = master.anchors
    try:
        return loader.get_single_data()
    finally:
        loader.dispose()

with open('warehouse.yaml') as fp:
    data = my_safe_load(fp)
yaml.safe_dump(data, sys.stdout, default_flow_style=False)

给出:

specific:
  spec1:
    key1: 1
    key2: 2
  spec2:
    key1: 10
    key2: 2
warehouse:
  obj1:
    key1: 1
    key2: 2

如果您的specific.yaml 没有***密钥specific

spec1:
  <<: *obj1
spec2:
  <<: *obj1
  key1: 10

然后将yaml_include()的最后一行替换为:

return my_safe_load(inputfile, master=loader)

以上是使用ruamel.yaml 完成的(免责声明:我是该软件包的作者)并在 Python 2.7 和 3.6 上进行了测试。通过更改导入,它也可以与 PyYAML 一起使用。


使用新的ruamel.yaml API,上面的内容可以大大简化,因为传递给yaml_include() 构造函数的loader 知道YAML 实例,但当然你仍然需要一个适应的compose_document,它不会不要破坏锚。假设specific.yaml 没有***键specific,下面的输出与以前相同。

import sys
from ruamel.std.pathlib import Path
from ruamel.yaml import YAML, version_info

yaml = YAML(typ='safe', pure=True)
yaml.default_flow_style = False


def my_compose_document(self):
    self.parser.get_event()
    node = self.compose_node(None, None)
    self.parser.get_event()
    # self.anchors =     # <<<< commented out
    return node

yaml.Composer.compose_document = my_compose_document

# adapted from http://code.activestate.com/recipes/577613-yaml-include-support/
def yaml_include(loader, node):
    y = loader.loader
    yaml = YAML(typ=y.typ, pure=y.pure)  # same values as including YAML
    yaml.composer.anchors = loader.composer.anchors
    return yaml.load(Path(node.value))

yaml.Constructor.add_constructor("!include", yaml_include)

data = yaml.load(Path('warehouse.yaml'))
yaml.dump(data, sys.stdout)

【讨论】:

正如对 activestate 配方的评论所表明的那样,包含机制远非健壮。至少应该继承YAML 以包含用于测试正在处理的文件的代码,以防止无限递归。通过使用typ='safe',您不能实例化任意对象,尽管按原样滥用!include 可能会使您的程序崩溃。 这篇文章很老了。尽管@maciej 最近发布了关于ramuel.yaml 扩展的更新,该更新添加了开箱即用的“!include”机制=> ramuel.yaml.include。但是,它没有得到维护。您是否打算将来在您的包中添加此类功能? @muxevola 我不知道那个答案,也不知道那里提到的存储库。有时间我会试着研究一下。

以上是关于PyYaml“包含文件”和 yaml 别名(锚点/引用)的主要内容,如果未能解决你的问题,请参考以下文章

Python的PyYAML模块详解

从文档中检索 yaml-cpp 中的锚点和别名字符串

使用 PyYAML 和 Python 的 YAML

为啥致命错误:安装 PyYAML 时找不到“yaml.h”文件?

在 PyYAML 中保存/转储带有注释的 YAML 文件

如果有“!”,如何使用 PyYAML 解析 YAML在 YAML 中