如何修改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
相关产品推荐
相关产品推荐

