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.yaml
和 specific.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 别名(锚点/引用)的主要内容,如果未能解决你的问题,请参考以下文章