【问题标题】:YAML anchor for sequence?用于序列的 YAML 锚?
【发布时间】:2021-07-02 17:31:58
【问题描述】:

考虑以下文件 YAML 文件:

A:
  &B
  - x: 1
    y: 2
  - x: 10
    y: 20

C:
  <<: *B

通过以下方式读入python:

from ruamel.yaml import YAML
filename = 'data/configs/debug_config.yml'

with open(filename) as f:
    c = YAML(typ='safe').load(f)

print(c)

屈服:

{'A': [{'x': 1, 'y': 2}, {'x': 10, 'y': 20}], 'C': {'x': 1, 'y': 2}}

可见anchorB只包含序列的第一个元素。为什么?我想要一个包含整个序列的锚点,这样 python 字典中 AC 的值是相同的。如何做到这一点?

【问题讨论】:

  • AC 相同是什么意思?您希望它们是同一个字典实例 (c['A'] is c['C']),还是只是键和值相同 (c['A'] == c['C'] and c['A'] not is c['C'])?
  • 这是一个有趣的观点。我的意图是它们应该是单独的字典。但是,在我的特定用例中,c 的任何部分一旦被读取,就不应进行任何修改。因此,如果 c[`A']c['C`] 是同一个字典,那没关系。但是很好奇如果这两个字典必须不同,我需要做什么。

标签: yaml pyyaml ruamel.yaml


【解决方案1】:

锚点B 包含来自A 的所有元素,但您正在使用合并键&lt;&lt; (source) 合并它们:

如果与合并键关联的值是一个序列,那么这个序列应该包含映射节点,并且这些节点中的每一个都按照其在序列中的顺序依次合并。 在序列中较早的映射节点中的键覆盖在后面的映射节点中指定的键。

所以,A 中的第一项覆盖了第二项。

删除&lt;&lt;:C 将是与A 相同的字典:

A: &B
  - x: 1
    y: 2
  - x: 10
    y: 20

C: *B

产量:

{
  "A": [
    {
      "y": 2, 
      "x": 1
    }, 
    {
      "y": 20, 
      "x": 10
    }
  ], 
  "C": [
    {
      "y": 2, 
      "x": 1
    }, 
    {
      "y": 20, 
      "x": 10
    }
  ]
}

您可以通过此示例检查合并顺序:

A: &B
  - x: 1
    y: 2
  - x: 10
    new: 333 


C: 
  <<: *B

产量:

{
  "A": [
    {
      "y": 2, 
      "x": 1
    }, 
    {
      "x": 10, 
      "new": 333
    }
  ], 
  "C": {
    "y": 2, 
    "x": 1, 
    "new": 333
  }
}

【讨论】:

  • 虽然分析正确,但“C 将包含来自A 的所有元素”有点误导。假设 OP 的加载代码片段,c['A'] 不仅包含与c['C'] 相同的键值对,而且它实际上是同一个字典。所以更改c['A'] 也会影响c['C']
  • 有没有明显的方法来确保字典是不同的?
【解决方案2】:

正如@Hadyniak 已经指出您错误地使用了merge keys。由于别名 *B 在 composer 步骤,在解释之前的&lt;&lt;merge 键 构造器步骤,后者实际上接收一个字典列表,它们是 结合较早出现的dicts的键值优先。如果 处理顺序碰巧不同,你会得到一个错误,并且 IMO 合并密钥文档没有明确指定别名应该 先展开。

Hadyniak 的解决方案会让你结束 c['A']c['C'] 是同一个字典,这可能不是你想要的:

import ruamel.yaml

yaml_str = """\
A:
  &B
  - x: 1
    y: 2
  - x: 10
    y: 20

C:
  *B
"""

yaml = ruamel.yaml.YAML()
c = yaml.load(yaml_str)
print(c['A'] is c['C'], c['A'] == c['C'])
c['A'][0]['x'] = 42
print('x:', c['C'][0]['x'])

给出:

True True
x: 42

如果这不是您想要的,您仍然可以使用合并键,但在作为序列元素的字典上:

import ruamel.yaml

yaml_str = """\
A:
  - &B1
    x: 1
    y: 2
  - &B2
    x: 10
    y: 20

C:
  - <<: *B1
  - <<: *B2
"""

yaml = ruamel.yaml.YAML()
c = yaml.load(yaml_str)
print(c['A'] is c['C'], c['A'] == c['C'])
c['A'][0]['x'] = 42
print('x:', c['C'][0]['x'])

给出:

False True
x: 1

或者,您可以告诉 ruamel.yaml 的作曲家部分扩展别名而不是使用引用:

import copy

yaml_str = """\
A:
  &B
  - x: 1
    y: 2
  - x: 10
    y: 20

C:
  *B
"""

yaml = ruamel.yaml.YAML()

yaml.composer.return_alias = lambda s: copy.deepcopy(s)

c = yaml.load(yaml_str)
print(c['A'] is c['C'], c['A'] == c['C'])
c['A'][0]['x'] = 42
print('x:', c['C'][0]['x'])

给出:

False True
x: 1

以上仅适用于ruamel.yaml&gt;0.17.2,对于旧版本,您需要复制和修改compose_node方法:

import copy

yaml_str = """\
A:
  &B
  - x: 1
    y: 2
  - x: 10
    y: 20

C:
  *B
"""

class MyComposer(ruamel.yaml.composer.Composer):
    def compose_node(self, parent, index):
        # type: (Any, Any) -> Any
        if self.parser.check_event(ruamel.yaml.events.AliasEvent):
            event = self.parser.get_event()
            alias = event.anchor
            if alias not in self.anchors:
                raise ComposerError(
                    None,
                    None,
                    'found undefined alias {alias!r}'.format(alias=alias),
                    event.start_mark,
                )
            return copy.deepcopy(self.anchors[alias])
        event = self.parser.peek_event()
        anchor = event.anchor
        if anchor is not None:  # have an anchor
            if anchor in self.anchors:
                ws = (
                    '\nfound duplicate anchor {!r}\nfirst occurrence {}\nsecond occurrence '
                    '{}'.format((anchor), self.anchors[anchor].start_mark, event.start_mark)
                )
                warnings.warn(ws, ruamel.yaml.error.ReusedAnchorWarning)
        self.resolver.descend_resolver(parent, index)
        if self.parser.check_event(ruamel.yaml.events.ScalarEvent):
            node = self.compose_scalar_node(anchor)
        elif self.parser.check_event(ruamel.yaml.events.SequenceStartEvent):
            node = self.compose_sequence_node(anchor)
        elif self.parser.check_event(ruamel.yaml.events.MappingStartEvent):
            node = self.compose_mapping_node(anchor)
        self.resolver.ascend_resolver()
        return node



yaml = ruamel.yaml.YAML()
yaml.Composer = MyComposer

c = yaml.load(yaml_str)
print(c['A'] is c['C'], c['A'] == c['C'])
c['A'][0]['x'] = 42
print('x:', c['C'][0]['x'])

这也给出了:

False True
x: 1

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-03-23
    • 1970-01-01
    • 1970-01-01
    • 2016-11-01
    • 2017-02-25
    • 2022-12-15
    相关资源
    最近更新 更多