使用ruamel.yaml时__post_init__调用时fields字段未初始化问题
问题原因及解决办法
问题原因
核心原因是ruamel.yaml反序列化dataclass的顺序:当加载YAML数据到Msg实例时,ruamel.yaml会先调用Msg的__init__方法创建实例(此时fields通常被初始化为空列表,比如使用了field(default_factory=list)),再逐个解析并添加Field实例到fields列表中。而__post_init__是在__init__执行完毕后立即调用的,此时fields还未被填充,自然为空,导致无法更新index值。
如果Msg类中fields字段未设置default_factory,初始化时未传入值也会是空列表,但结合ruamel.yaml的使用场景,前者是更常见的触发原因。
基于类系统的解决办法
方法1:通过属性setter自动更新索引
给Msg类的fields字段定义setter,每当fields被赋值(包括ruamel.yaml填充时),自动遍历列表设置每个Field的index:
from dataclasses import dataclass, field import ruamel.yaml @dataclass class Field: name: str index: int = -1 # 默认值标记未初始化 @dataclass class Msg: _fields: list[Field] = field(default_factory=list, repr=False) @property def fields(self): return self._fields @fields.setter def fields(self, value): self._fields = value # 遍历设置索引 for idx, field in enumerate(self._fields): field.index = idx # 测试代码 yaml_str = """ fields: - name: field1 - name: field2 """ yaml = ruamel.yaml.YAML() msg = yaml.load(yaml_str) assert msg.fields[0].index == 0 assert msg.fields[1].index == 1
方法2:定义手动初始化方法
在Msg类中添加专门的索引初始化方法,在ruamel.yaml加载完成后手动调用:
from dataclasses import dataclass, field import ruamel.yaml @dataclass class Field: name: str index: int = -1 @dataclass class Msg: fields: list[Field] = field(default_factory=list) def init_field_indices(self): for idx, field in enumerate(self.fields): field.index = idx # 测试代码 yaml_str = """ fields: - name: field1 - name: field2 """ yaml = ruamel.yaml.YAML() msg = yaml.load(yaml_str) msg.init_field_indices() assert msg.fields[0].index == 0 assert msg.fields[1].index == 1
方法3:利用ruamel.yaml自定义构造器
若不想修改类内部结构,可给Msg类注册自定义构造器,在反序列化完成后自动触发索引初始化:
from dataclasses import dataclass, field import ruamel.yaml from ruamel.yaml.constructor import SafeConstructor @dataclass class Field: name: str index: int = -1 @dataclass class Msg: fields: list[Field] = field(default_factory=list) def init_field_indices(self): for idx, field in enumerate(self.fields): field.index = idx # 注册自定义构造器 def msg_constructor(loader, node): msg = loader.construct_yaml_object(node, Msg) msg.init_field_indices() return msg SafeConstructor.add_constructor('!Msg', msg_constructor) # 测试YAML需添加对应标签 yaml_str = """ !Msg fields: - name: field1 - name: field2 """ yaml = ruamel.yaml.YAML() msg = yaml.load(yaml_str) assert msg.fields[0].index == 0 assert msg.fields[1].index == 1
注意事项
- 方法1无需额外调用,自动完成索引更新,但需将
fields改为私有属性加setter; - 方法2实现最简单,仅需添加方法并在加载后调用;
- 方法3适合不愿修改类结构的场景,但需给YAML数据添加对应标签。
内容的提问来源于stack exchange,提问作者FibroMyAlgebra
相关产品推荐
相关产品推荐

