我想在YAML中合并数组,并通过ruby加载它们 -
some_stuff: &some_stuff
- a
- b
- c
combined_stuff:
<<: *some_stuff
- d
- e
- f
我想将组合数组设为[a,b,c,d,e,f]
我收到错误:解析块映射时找不到预期的键
如何在YAML中合并数组?
答案 0 :(得分:17)
本文假设以下背景:
lfender6445希望合并YAML文件中的两个或多个列表,并拥有这些列表 合并列表在解析时显示为一个单数列表。
这可以通过将YAML锚分配给映射来获得,其中 所需列表显示为映射的子元素。然而,有一些警告(参见&#34;陷阱&#34; infra)。
在下面的示例中,我们有三个映射(list_one, list_two, list_three
)和三个锚点
和适当引用这些映射的别名。
当YAML文件加载到程序中时,我们得到了我们想要的列表,但是 加载后可能需要稍加修改(见下面的陷阱)。
list_one: &id001 - a - b - c list_two: &id002 - e - f - g list_three: &id003 - h - i - j list_combined: - *id001 - *id002 - *id003
## list_combined [ [ "a", "b", "c" ], [ "e", "f", "g" ], [ "h", "i", "j" ] ]
此方法允许使用YAML的别名和锚点功能创建合并列表。
虽然输出结果是嵌套的列表列表,但可以使用flatten
方法轻松转换。
flatten
方法flatten
;; Merge/flatten an array of arrays flatten
;; http://ruby-doc.org/core-2.2.2/Array.html#method-i-flatten flatten
;; https://softwareengineering.stackexchange.com/a/254676/23884 答案 1 :(得分:10)
这不起作用:
merge,而不支持序列
您通过合并键<<
完全混合了一些东西
后跟键/值分隔符:
和值为a的值
引用然后继续使用相同缩进的列表
水平强>
这不正确YAML:
combine_stuff:
x: 1
- a
- b
因此,您的示例语法甚至不能用作YAML扩展提议。
如果您想要合并多个数组,可能需要考虑如下语法:
combined_stuff:
- <<: *s1, *s2
- <<: *s3
- d
- e
- f
其中s1
,s2
,s3
是您的序列(未显示)的锚点
想要合并为一个新序列,然后拥有d
,e
和f
附加到那。但是YAML正在解决这些结构的深度问题
首先,因此在处理过程中没有真正的上下文
合并密钥。您没有可用的阵列/列表
可以将处理后的值(锚定序列)附加到。
您可以采用@dreflymac提出的方法,但这会给您带来巨大的劣势 不知何故需要知道哪些嵌套序列变平(即通过从根知道&#34;路径&#34;) 加载的数据结构到父序列),或者递归地遍历加载的数据结构 数据结构搜索嵌套数组/列表并不加区别地压平它们。
更好的解决方案IMO将使用标签来加载数据结构 为你做扁平化这允许清楚地表示什么 需要被夷为平地,什么不是,并让你完全控制 这种展平是在装载过程中完成还是在完成过程中完成 访问。选择哪一个是易于实施的问题 时间和存储空间的效率。这是需要进行的相同交易 用于实现merge key feature和 没有单一的解决方案永远是最好的。
E.g。我的ruamel.yaml
库在使用期间使用了强力合并
使用安全加载程序时加载,导致合并
字典是普通的Python dicts。这种合并必须要做
预先,并复制数据(空间效率低),但价值快
抬头。使用往返加载程序时,您希望能够转储
合并未合并,所以他们需要保持独立。这个词典就像
由于往返加载而加载的数据结构是空间
高效但访问速度较慢,因为它需要尝试查找密钥
在合并中没有在dict中找到(并且这不是缓存的,所以
它需要每次都完成)。当然这样的考虑是
对于相对较小的配置文件不是很重要。
下面使用标记为flatten
的对象为python中的列表实现类似合并的方案
即时递归到列表和标记toflatten
的项目。使用这两个标签
你可以有YAML文件:
l1: &x1 !toflatten
- 1
- 2
l2: &x2
- 3
- 4
m1: !flatten
- *x1
- *x2
- [5, 6]
- !toflatten [7, 8]
(使用flow vs block样式序列完全是任意的,并且对它没有影响 加载结果)。
当迭代作为键m1
的值的项时
&#34;递归&#34;进入标有toflatten
的序列,但显示
其他列表(别名或非别名)作为单个项目。
Python代码实现这一目标的一种可能方式是:
import sys
from pathlib import Path
import ruamel.yaml
yaml = ruamel.yaml.YAML()
@yaml.register_class
class Flatten(list):
yaml_tag = u'!flatten'
def __init__(self, *args):
self.items = args
@classmethod
def from_yaml(cls, constructor, node):
x = cls(*constructor.construct_sequence(node, deep=True))
return x
def __iter__(self):
for item in self.items:
if isinstance(item, ToFlatten):
for nested_item in item:
yield nested_item
else:
yield item
@yaml.register_class
class ToFlatten(list):
yaml_tag = u'!toflatten'
@classmethod
def from_yaml(cls, constructor, node):
x = cls(constructor.construct_sequence(node, deep=True))
return x
data = yaml.load(Path('input.yaml'))
for item in data['m1']:
print(item)
输出:
1
2
[3, 4]
[5, 6]
7
8
正如您所看到的,您可以按照需要展平的顺序看到 可以使用标记序列的别名,也可以使用标记 序列。 YAML不允许你这样做:
- !flatten *x2
,即标记一个 锚定序列,因为这基本上使它成为一个不同的 数据结构。
使用显式标签比IMO更好
使用YAML合并键<<
。如果没有其他你现在必须通过
如果您碰巧有一个带有密钥的映射的YAML文件,那就会发生错误
您不想表现为合并密钥的<<
,例如当你做一个
C运算符映射到英语(或其他一些自然语言)的描述。
答案 2 :(得分:1)
另一种在 python 中启用合并数组的方法是定义一个 !flatten
标签。
(这使用 PyYAML,与上面 Anthon 的回答不同。如果您无法控制在支持中使用哪个包,例如 anyconfig
,这可能是必要的。
import yaml
yaml.add_constructor("!flatten", construct_flat_list)
def flatten_sequence(sequence: yaml.Node) -> Iterator[str]:
"""Flatten a nested sequence to a list of strings
A nested structure is always a SequenceNode
"""
if isinstance(sequence, yaml.ScalarNode):
yield sequence.value
return
if not isinstance(sequence, yaml.SequenceNode):
raise TypeError(f"'!flatten' can only flatten sequence nodes, not {sequence}")
for el in sequence.value:
if isinstance(el, yaml.SequenceNode):
yield from flatten_sequence(el)
elif isinstance(el, yaml.ScalarNode):
yield el.value
else:
raise TypeError(f"'!flatten' can only take scalar nodes, not {el}")
def construct_flat_list(loader: yaml.Loader, node: yaml.Node) -> List[str]:
"""Make a flat list, should be used with '!flatten'
Args:
loader: Unused, but necessary to pass to `yaml.add_constructor`
node: The passed node to flatten
"""
return list(flatten_sequence(node))
这种递归扁平化利用了 PyYAML 文档结构,它将所有数组解析为 SequenceNode
,所有值解析为 ScalarNode
。
可以在以下测试函数中测试(和修改)该行为。
import pytest
def test_flatten_yaml():
# single nest
param_string = """
bread: &bread
- toast
- loafs
chicken: &chicken
- *bread
midnight_meal: !flatten
- *chicken
- *bread
"""
params = yaml.load(param_string)
assert sorted(params["midnight_meal"]) == sorted(
["toast", "loafs", "toast", "loafs"]
)
答案 3 :(得分:0)
如果您只需要将一项合并到列表中,则可以
fruit:
- &banana
name: banana
colour: yellow
food:
- *banana
- name: carrot
colour: orange
产生
fruit:
- name: banana
colour: yellow
food:
- name: banana
colour: yellow
- name: carrot
colour: orange
答案 4 :(得分:0)
您可以按照以下步骤实现:
# note: no dash before commands
some_stuff: &some_stuff |-
a
b
c
combined_stuff:
- *some_stuff
- d
- e
- f
我一直在gitlab-ci.yml
上使用它(回答有关问题的@ rink.attendant.6评论)。
答案 5 :(得分:0)
这是我在 docker-compose.yml 文件中实现它的方法。
“合并列表”部分位于环境变量部分。我认为棘手(也很酷的部分)是我能够在 *snapshots-repository-path
中使用对 &es-env-base
的引用。
一般而言 - 更喜欢使用 long syntax 而不是 short syntax,因为它支持使用 YAML *references
。
version: "3.7"
### ------------------------------------------------------------------
### Variables
### ------------------------------------------------------------------
x-variables:
exposed-port: &exposed-port 9200
es-base: &es-base
image: docker.elastic.co/elasticsearch/elasticsearch:7.9.1
ulimits:
memlock:
soft: -1
hard: -1
networks:
- elastic
data-path: &data-path /usr/share/elasticsearch/data
snapshots-repository-path: &snapshots-repository-path /usr/share/elasticsearch/backup
volume-snapshots-repository: &volume-snapshots-repository
- type: volume
source: snapshots-repository
target: *snapshots-repository-path
services-es-env: &es-env-base
"cluster.name": "es-docker-cluster"
"cluster.initial_master_nodes": "es01,es02"
"bootstrap.memory_lock": "true"
"ES_JAVA_OPTS": "-Xms512m -Xmx512m"
"ELASTIC_PASSWORD": "esbackup-password"
"xpack.security.enabled": "true"
"path.repo": *snapshots-repository-path
### ------------------------------------------------------------------
services:
es01: # master
<<: *es-base
container_name: es01
environment:
<<: *es-env-base
node.name: es01
discovery.seed_hosts: es02
volumes:
- <<: *volume-snapshots-repository
- type: volume
source: data01
target: *data-path
ports:
- published: *exposed-port
target: 9200
protocol: tcp
mode: host
es02:
<<: *es-base
container_name: es02
environment:
<<: *es-env-base
node.name: es02
discovery.seed_hosts: es01
volumes:
- <<: *volume-snapshots-repository
- type: volume
source: data02
target: *data-path
volumes:
data01:
driver: local
data02:
driver: local
snapshots-repository:
driver: local
networks:
elastic:
driver: bridge
答案 6 :(得分:-1)
您可以合并映射,然后在以下条件下将其密钥转换为列表:
x/def