在 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)