如何合并YAML数组



我想在YAML中合并数组,并通过ruby加载它们-

some_stuff: &some_stuff
 - a
 - b
 - c
combined_stuff:
  <<: *some_stuff
  - d
  - e
  - f

我想让组合数组为[a,b,c,d,e,f]

我收到错误:在解析块映射时没有找到期望的键

如何在YAML中合并数组?

如果目标是运行一系列shell命令,您可以这样做:

# note: no dash before commands
some_stuff: &some_stuff |-
    a
    b
    c
combined_stuff:
  - *some_stuff
  - d
  - e
  - f

这相当于:

some_stuff: "anbnc"
combined_stuff:
  - "anbnc"
  - d
  - e
  - f

我一直在我的gitlab-ci.yml上使用这个来回答@rink.侍者。6 .


我们用来支持requirements.txt从gitlab私有repos的工作示例:

.pip_git: &pip_git
- git config --global url."https://gitlab-ci-token:${CI_JOB_TOKEN}@gitlab.com".insteadOf "ssh://git@gitlab.com"
- mkdir -p ~/.ssh
- chmod 700 ~/.ssh
- echo "$SSH_KNOWN_HOSTS" > ~/.ssh/known_hosts
- chmod 644 ~/.ssh/known_hosts
test:
    image: python:3.7.3
    stage: test
    script:
        - *pip_git
        - pip install -q -r requirements_test.txt
        - python -m unittest discover tests
use the same `*pip_git` on e.g. build image...

其中requirements_test.txt包含例如

-e git+ssh://git@gitlab.com/example/example.git@v0.2.2#egg=example

这行不通:

  1. merge只被YAML规范用于映射,而不用于序列

  2. 你完全混合的事情有一个合并键<<然后是键/值分隔符:和a引用,然后在相同的缩进处继续使用列表水平

这是不正确的YAML:

combine_stuff:
  x: 1
  - a
  - b

所以你的示例语法甚至没有意义作为一个YAML扩展建议。

如果你想做一些事情,比如合并多个数组,你可能想考虑这样的语法:

combined_stuff:
  - <<: *s1, *s2
  - <<: *s3
  - d
  - e
  - f

,其中s1, s2, s3是序列上的锚点(未显示)我想合并成一个新的序列,然后有d, ef附在上面。但是YAML解决了这些结构的深度首先,在处理过程中没有真正的上下文可用合并键的。没有可供您使用的数组/列表可以将处理后的值(锚定序列)附加到。

您可以采用@dreftymac所建议的方法,但这有一个巨大的缺点,即您无法使用该方法不知何故,需要知道哪些嵌套序列要平坦化(即通过知道"路径";从词根开始加载的数据结构到父序列),或者递归地遍历加载的查找嵌套数组/列表的数据结构,并不分青红皂白地将它们平面化。

在我看来,更好的解决方案是使用标签来加载数据结构帮你压平。这允许清楚地表示什么需要被压平和不给你完全的控制这种压平是在加载过程中完成的,还是在加载过程中完成的访问。选择哪一个是一个容易实现的问题时间和存储空间的效率。这是需要做的同样的权衡用于实现merge key特性和没有唯一的解决办法永远是最好的。

。我的ruamel.yaml库在期间使用暴力合并字典加载时使用它的安全加载程序,这将导致合并普通的Python字典。这种合并必须完成预先,并重复数据(空间效率低),但值很快查找。在使用往返加载程序时,您希望能够转储数据合并未合并,因此它们需要保持独立。字典喜欢由于往返加载而加载的数据结构是空间效率高,但访问速度较慢,因为它需要尝试查找键在合并中没有在字典本身中找到(并且没有缓存,所以每次都需要这样做)。当然,这样的考虑是对于相对较小的配置文件来说不是很重要。


下面的代码使用标记为flatten的对象在python中实现了一个类似于合并的列表方案它动态递归为标记为toflatten的列表项。使用以下两个标签你可以有YAML文件:

l1: &x1 !toflatten
  - 1 
  - 2
l2: &x2
  - 3 
  - 4
m1: !flatten
  - *x1
  - *x2
  - [5, 6]
  - !toflatten [7, 8]

(流和块样式序列的使用完全是任意的,并且不会影响加载结果).

当遍历为键m1 this的值的项时"recurses"标记为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

,即标签an锚定序列,因为这实质上会使它变成一个不同的数据结构。

使用显式标签在我看来比使用一些魔术效果更好与YAML合并键<<。如果没有别的事,你现在必须经历如果您碰巧有一个带有键的映射的YAML文件,那么就会出现这种情况<<,你不想像一个合并键,例如,当你做一个C操作符到其英文(或其他自然语言)描述的映射。

更新:2019-07-01 14:06:12

  • 注释:对这个问题的另一个答案进行了大量编辑,并更新了替代方法。
    • 这个更新的答案提到了这个答案中解决方法的另一种选择。它已被添加到,参见下面的部分。
<

上下文/h2>

这篇文章假设以下上下文:

    python 2.7
  • python YAML解析器

lfender6445希望在一个YAML文件中合并两个或多个列表,并拥有它们合并列表在解析时显示为一个单一列表。

解决方案(解决方案)

可以简单地通过将YAML锚分配给映射来获得,其中所需列表作为映射的子元素出现。然而,这里有一些警告(参见下面的"陷阱")。

在下面的示例中,我们有三个映射(list_one, list_two, list_three)和三个锚以及在适当的地方引用这些映射的别名。

当YAML文件加载到程序中时,我们得到了想要的列表,但是它可能需要在加载后进行一些修改(见下面的陷阱)。

原始YAML文件

<>之前list_one: &id001——一个- b- clist_two: &id002- e- f- glist_three: &id003- - - - - - h——我- - - - - - jlist_combined:- * id001- * id002- * id003之前

YAML.safe_load后的结果

<>之前# # list_combined[["一个","b","c"],["e","f","g"],["h","我","j"]]之前

陷阱
  • 这种方法产生一个嵌套的列表列表,它可能不是确切想要的输出,但可以使用flatten方法进行后处理
  • 对YAML锚和别名的通常警告适用于唯一性和声明顺序
结论

这种方法允许使用YAML的别名和锚特性创建合并列表。

虽然输出结果是一个嵌套的列表列表,但可以使用flatten方法轻松转换。

参见

@Anthon更新的替代方法

  • 参见替代方法

flatten方法示例

  • Javascript flatten;;合并/平整化数组数组
  • Ruby flatten;http://ruby-doc.org/core-2.2.2/Array.html method-i-flatten
  • Python flatten;;https://softwareengineering.stackexchange.com/a/254676/23884

如果您只需要合并一个项目到列表中,您可以这样做

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

在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 s,并将所有值解析为ScalarNode s。行为可以在下面的测试函数中进行测试(和修改)。

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"]
    )

您可以合并映射,然后将它们的键转换为列表,在以下条件下:

  • 如果你使用jinja2模板和
  • 如果项目顺序不重要
some_stuff: &some_stuff
 a:
 b:
 c:
combined_stuff:
  <<: *some_stuff
  d:
  e:
  f:
{{ combined_stuff | list }}

最新更新