序列的 YAML 锚点?
YAML anchor for sequence?
考虑以下文件 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}}
可见anchor B
只包含序列的第一个元素。为什么?我想要一个包含整个序列的锚点,这样 python 字典中 A
和 C
的值是相同的。如何做到这一点?
锚 B
包含来自 A
的所有元素,但您正在使用合并键 <<
(source):
合并它们
If the value associated with the merge key is a sequence, then this sequence is expected to contain mapping nodes and each of these nodes is merged in turn according to its order in the sequence. Keys in mapping nodes earlier in the sequence override keys specified in later mapping nodes.
因此,A
中的第一项会覆盖第二项。
删除 <<:
并且 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
}
}
正如@Hadyniak 已经指出的那样,您错误地使用了 merge
键。由于别名 *B
在
composer 步骤,在解释 <<
合并键之前
构造函数步骤,后者实际上接收到一个字典列表,它们是
结合较早出现的字典的键值优先。如果
处理的顺序恰好不同你会得到一个错误,并且
IMO 合并密钥文档没有明确指定别名应该
先展开。
哈迪尼亚克的解决方案会让你结束
up with c['A']
and c['C']
being the same dictionary and that that might not be what you want:
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>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
考虑以下文件 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}}
可见anchor B
只包含序列的第一个元素。为什么?我想要一个包含整个序列的锚点,这样 python 字典中 A
和 C
的值是相同的。如何做到这一点?
锚 B
包含来自 A
的所有元素,但您正在使用合并键 <<
(source):
If the value associated with the merge key is a sequence, then this sequence is expected to contain mapping nodes and each of these nodes is merged in turn according to its order in the sequence. Keys in mapping nodes earlier in the sequence override keys specified in later mapping nodes.
因此,A
中的第一项会覆盖第二项。
删除 <<:
并且 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
}
}
正如@Hadyniak 已经指出的那样,您错误地使用了 merge
键。由于别名 *B
在
composer 步骤,在解释 <<
合并键之前
构造函数步骤,后者实际上接收到一个字典列表,它们是
结合较早出现的字典的键值优先。如果
处理的顺序恰好不同你会得到一个错误,并且
IMO 合并密钥文档没有明确指定别名应该
先展开。
哈迪尼亚克的解决方案会让你结束
up with c['A']
and c['C']
being the same dictionary and that that might not be what you want:
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>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