我有两个YAML文件,“A”和“B”,我希望将A的内容插入到B中,要么拼接到现有的数据结构中,就像数组一样,要么作为元素的子元素,就像某个散列键的值一样。
这可能吗?怎么做?如果不是,是否有指向规范引用的指针?
我有两个YAML文件,“A”和“B”,我希望将A的内容插入到B中,要么拼接到现有的数据结构中,就像数组一样,要么作为元素的子元素,就像某个散列键的值一样。
这可能吗?怎么做?如果不是,是否有指向规范引用的指针?
当前回答
加上上面@Joshbode的初始回答,我对代码片段进行了一些修改,以支持UNIX风格的通配符模式。
不过我还没有在windows中进行测试。为了便于维护,我面临着将大型yaml中的数组拆分到多个文件中的问题,并正在寻找一种解决方案,以便在基本yaml的同一个数组中引用多个文件。因此,下面的解决方案。解决方案不支持递归引用。它只支持在基本yaml中引用的给定目录级别中的通配符。
import yaml
import os
import glob
# Base code taken from below link :-
# Ref:https://stackoverflow.com/a/9577670
class Loader(yaml.SafeLoader):
def __init__(self, stream):
self._root = os.path.split(stream.name)[0]
super(Loader, self).__init__(stream)
def include(self, node):
consolidated_result = None
filename = os.path.join(self._root, self.construct_scalar(node))
# Below section is modified for supporting UNIX wildcard patterns
filenames = glob.glob(filename)
# Just to ensure the order of files considered are predictable
# and easy to debug in case of errors.
filenames.sort()
for file in filenames:
with open(file, 'r') as f:
result = yaml.load(f, Loader)
if isinstance(result, list):
if not isinstance(consolidated_result, list):
consolidated_result = []
consolidated_result += result
elif isinstance(result, dict):
if not isinstance(consolidated_result, dict):
consolidated_result = {}
consolidated_result.update(result)
else:
consolidated_result = result
return consolidated_result
Loader.add_constructor('!include', Loader.include)
使用
a:
!include a.yaml
b:
# All yamls included within b folder level will be consolidated
!include b/*.yaml
其他回答
使用Symfony,它对yaml的处理将间接地允许您嵌套yaml文件。诀窍在于使用参数选项。例如:
common.yml
parameters:
yaml_to_repeat:
option: "value"
foo:
- "bar"
- "baz"
config.yml
imports:
- { resource: common.yml }
whatever:
thing: "%yaml_to_repeat%"
other_thing: "%yaml_to_repeat%"
其结果将与:
whatever:
thing:
option: "value"
foo:
- "bar"
- "baz"
other_thing:
option: "value"
foo:
- "bar"
- "baz"
对于Python用户,可以尝试pyyaml-include。
安装
pip install pyyaml-include
使用
import yaml
from yamlinclude import YamlIncludeConstructor
YamlIncludeConstructor.add_to_loader_class(loader_class=yaml.FullLoader, base_dir='/your/conf/dir')
with open('0.yaml') as f:
data = yaml.load(f, Loader=yaml.FullLoader)
print(data)
假设我们有这样的YAML文件:
├── 0.yaml
└── include.d
├── 1.yaml
└── 2.yaml
1.Yaml的内容:
name: "1"
2.Yaml的内容:
name: "2"
按名称包含文件
顶层: 如果0。yaml是:
!include include.d/1.yaml
我们会得到:
{"name": "1"}
在映射: 如果0。yaml是:
file1: !include include.d/1.yaml
file2: !include include.d/2.yaml
我们会得到:
file1:
name: "1"
file2:
name: "2"
在序列: 如果0。yaml是:
files:
- !include include.d/1.yaml
- !include include.d/2.yaml
我们会得到:
files:
- name: "1"
- name: "2"
ℹ注意: 文件名可以是绝对的(如/usr/conf/1.5/ make .yml)或相对的(如../../cfg/img.yml)。
通过通配符包含文件
文件名可以包含shell样式的通配符。从通配符找到的文件中加载的数据将按顺序设置。
如果0。yaml是:
files: !include include.d/*.yaml
我们会得到:
files:
- name: "1"
- name: "2"
ℹ注意: 对于Python>=3.5,如果递归参数!include YAML标记为真,模式" ** "将匹配任何文件和零个或多个目录和子目录。 在大型目录树中使用“**”模式可能会因为递归搜索而消耗过多的时间。
为了启用递归参数,我们应该在映射或序列模式下编写!include标记:
序列模式参数:
!include [tests/data/include.d/**/*.yaml, true]
映射模式参数说明
!include {pathname: tests/data/include.d/**/*.yaml, recursive: true}
不幸的是,YAML在其标准中没有提供这个功能。
但是如果你正在使用Ruby,有一个gem通过扩展Ruby YAML库来提供你所要求的功能: https://github.com/entwanderer/yaml_extend
不,标准YAML不包括任何类型的“import”或“include”语句。
您的问题没有要求使用Python解决方案,但这里有一个使用PyYAML的解决方案。
PyYAML允许您将自定义构造函数(例如!include)附加到YAML加载器。我已经包含了一个可以设置的根目录,以便这个解决方案支持相对和绝对文件引用。
基于类的解决方案
这是一个基于类的解决方案,避免了我原始响应的全局根变量。
请参阅以下要点,了解一个类似的、更健壮的Python 3解决方案,该解决方案使用元类注册自定义构造函数。
import yaml
import os
class Loader(yaml.SafeLoader):
def __init__(self, stream):
self._root = os.path.split(stream.name)[0]
super(Loader, self).__init__(stream)
def include(self, node):
filename = os.path.join(self._root, self.construct_scalar(node))
with open(filename, 'r') as f:
return yaml.load(f, Loader)
Loader.add_constructor('!include', Loader.include)
一个例子:
foo.yaml
a: 1
b:
- 1.43
- 543.55
c: !include bar.yaml
bar.yaml
- 3.6
- [1, 2, 3]
现在可以使用以下方法加载文件:
>>> with open('foo.yaml', 'r') as f:
>>> data = yaml.load(f, Loader)
>>> data
{'a': 1, 'b': [1.43, 543.55], 'c': [3.6, [1, 2, 3]]}