You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何修改ruamel.yaml加载器实现!fullname标记标量映射的合并?

解决ruamel.yaml中标记标量无法通过<<合并的问题

问题说明

编写的ruamel.yaml加载器通过!fullname标记可将姓名标量转换为包含姓名各部分的映射,但尝试使用<<将该映射合并到外层对象时,抛出ConstructorError,提示expected a mapping or list of mappings for merging, but found scalar。

问题根源

ruamel.yaml默认的映射合并逻辑会优先检查<<指向的节点类型,仅接受MappingNode或包含MappingNode的SequenceNode。即便标记标量构造后会生成映射,也会因原始节点为ScalarNode直接触发错误,不会执行后续构造逻辑。

解决方案

重写构造器的construct_mapping方法,调整合并逻辑:先构造<<指向的节点(忽略原始节点类型),再检查构造后的对象是否为映射或映射列表,符合条件则执行合并操作。

完整代码示例

import ruamel.yaml
from ruamel.yaml.constructor import Constructor
from ruamel.yaml.nodes import MappingNode

class MyConstructor(Constructor):
    def construct_mapping(self, node, deep=False):
        if not isinstance(node, MappingNode):
            raise self.ConstructorError(
                None, None,
                "expected a mapping node, but found {}".format(node.id),
                node.start_mark
            )
        mapping = {}
        merge = []
        for key_node, value_node in node.value:
            key = self.construct_object(key_node, deep=False)
            if key == '<<':
                # 先构造节点对象,再判断类型是否符合合并要求
                value = self.construct_object(value_node, deep=False)
                if isinstance(value, dict):
                    merge.append(value)
                elif isinstance(value, list):
                    for elem in value:
                        if not isinstance(elem, dict):
                            raise self.ConstructorError(
                                "while constructing a mapping", node.start_mark,
                                f"expected a mapping or list of mappings for merging, but found {type(elem).__name__}",
                                value_node.start_mark
                            )
                        merge.append(elem)
                else:
                    raise self.ConstructorError(
                        "while constructing a mapping", node.start_mark,
                        f"expected a mapping or list of mappings for merging, but found {type(value).__name__}",
                        value_node.start_mark
                    )
            else:
                mapping[key] = self.construct_object(value_node, deep=deep)
        # 将合并内容整合到外层映射
        for m in merge:
            mapping.update(m)
        return mapping

class MyLoader(ruamel.yaml.YAML):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.constructor = MyConstructor()
        self.constructor.add_constructor("!fullname", self._fullname_constructor)

    @staticmethod
    def _fullname_constructor(constructor, node):
        value = constructor.construct_scalar(node)
        first, *middle, last = value.split()
        return {
            "first_name": first,
            "middle_names": middle,
            "last_name": last
        }

# 测试代码
myyaml = MyLoader()
result = myyaml.load("""
id: 0
<<: !fullname Albus Percival Wulfric Brian Dumbledore
""")
print(result)
# 输出: {'id': 0, 'first_name': 'Albus', 'middle_names': ['Percival', 'Wulfric', 'Brian'], 'last_name': 'Dumbledore'}

效果说明

修改后的构造器会先执行!fullname标记的构造逻辑,将标量转换为映射,再将该映射合并到外层对象中,彻底解决了原始节点类型导致的合并错误。

内容的提问来源于stack exchange,提问作者warownia1

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.13 13:05:56