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

ruamel.yaml加载嵌套自定义Python对象时数组字段解析为空的问题

问题描述

在嵌套结构中从YAML配置实例化Python对象数组时,非嵌套场景可正常执行,但YAML加载器无法递归遍历自定义对象的子节点,无论传入何种类型的数组,自定义对象from_yaml构造逻辑中读取到的数组字段始终为空。

问题复现代码

import yaml
import ruamel.yaml

class Person:
    def __init__(self, name: str = 'JohnDoe'):
        self.name = name

    @classmethod
    def from_yaml(cls, constructor, node):
        for m in constructor.construct_yaml_map(node):
            pass
        if 'Name' in m:
            name = m['Name']

        return cls(name=name)

    def __repr__(self):
        return f'Person(name={self.name})'


class Car:
    def __init__(self):
        self.passengers = []

    def add_person(self, person: Person = None):
        self.passengers.append(person)

    @classmethod
    def from_yaml(cls, constructor, node):
        for m in constructor.construct_yaml_map(node):
            pass

        inst = cls()

        if 'Driver' in m:
            inst.passengers = [m['Driver']]+inst.passengers

        if 'Passengers' in m:
            foo = m['Passengers']
            print(f'm["Passengers"] = {foo}')
            for person in m['Passengers']:
                inst.add_person(person)

        return inst

    def __repr__(self):
        return f'Car(passengers={self.passengers})'


if __name__ == "__main__":

    yaml = ruamel.yaml.YAML(typ='safe')
    yaml.register_class(Person)
    yaml.register_class(Car)

    data = yaml.load("""
        - !Person &0
            Name: 'Paul'

        - !Person &1
            Name: 'George'
            
        - !Person &3
            Name: 'John'

        - !Car
            Driver: *0
            Passengers: [*1]

        - !Car
            Driver: *3
            Passengers: 
                - !Person &4
                    Name: 'Ringo'

        """)

    print(f'data = {data}')

实际运行输出

m['Passengers'] = []
m['Passengers'] = []
data = [Person(name=Paul), Person(name=George), Person(name=John), Car(passengers=[Person(name=Paul)]), Car(passengers=[Person(name=John)])]

预期输出

m['Passengers'] = [Person(name=George)]
m['Passengers'] = [Person(name=Ringo)]
data = [Person(name=Paul), Person(name=George), Person(name=John), Car(passengers=[Person(name=Paul), Person(name=George)]), Car(passengers=[Person(name=John), Person(name=Ringo)])]

核心疑问

是否需要在from_yaml方法中手动指定构造器优先遍历剩余子节点,还是YAML加载器本身采用自底向上的递归加载逻辑?

问题原因

ruamel.yaml的构造器默认采用两阶段构造逻辑处理映射、序列等集合类型:

  • 第一阶段先创建集合的空实例(空字典、空列表),立即返回给上层调用方,用于解决循环引用、锚点引用的问题
  • 第二阶段再递归遍历集合内的子节点,逐步填充集合内容

现有from_yaml实现中,遍历完construct_yaml_map返回的生成器后,立刻读取Passengers字段的值,此时第二阶段的子节点构造还未执行,因此拿到的是空列表。

修复方案

在读取映射内的嵌套字段前,显式调用构造器的深度构造方法,递归生成所有子节点后再读取值,修改Car类的from_yaml方法如下:

@classmethod
def from_yaml(cls, constructor, node):
    # 递归构造当前节点下所有子节点,生成完整的映射对象
    m = constructor.construct_mapping(node, deep=True)
    inst = cls()

    if 'Driver' in m:
        inst.passengers = [m['Driver']] + inst.passengers

    if 'Passengers' in m:
        foo = m['Passengers']
        print(f'm["Passengers"] = {foo}')
        for person in m['Passengers']:
            inst.add_person(person)

    return inst

Person类的from_yaml方法也可以同步简化为同样的写法,去掉迭代生成器的冗余逻辑:

@classmethod
def from_yaml(cls, constructor, node):
    m = constructor.construct_mapping(node, deep=True)
    name = m.get('Name', 'JohnDoe')
    return cls(name=name)

修改后运行代码即可得到预期输出。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.30 14:03:20