如何让ruamel.yaml加载YAML时调用类的__post_init__方法?
解决ruamel.yaml加载dataclass时触发__post_init__的问题
问题描述
原本用ruamel.yaml从YAML文件构建dataclass对象运行正常,但尝试通过__post_init__方法随机选择可选值时,该方法未被调用。原因是ruamel.yaml默认的反序列化机制不会调用类的构造器,而是直接创建空实例并设置属性,导致依赖__init__触发的__post_init__无法执行。
用户提供的代码及配置如下:
Python代码
import random from abc import ABC, abstractmethod from dataclasses import dataclass from ruamel.yaml import YAML, yaml_object yaml = YAML() @dataclass class A(ABC): some_var: str options: list[str] def __post_init__(self): self.other_var = random.choice(self.options) @yaml_object(yaml) @dataclass class B(A): yaml_tag = "!B" more: int @yaml_object(yaml) @dataclass class C(A): yaml_tag = "!C" foo: list[int] def __post_init__(self): super().__post_init__() self.bar = random.choice(self.foo) # … data = yaml.load("config.yml")
YAML配置(config.yml)
test_classes: - !B some_var: abc123 options: ['X', 'Y', 'Z'] more: 7 - !C some_var: abc123 options: ['X', 'Y', 'Z'] foo: [7, 12, 42]
解决方案
要触发__post_init__,需让ruamel.yaml调用类的构造器实例化对象。为每个标记类添加自定义的from_yaml类方法,手动调用构造器完成实例化即可。
修改后的代码如下:
import random from abc import ABC, abstractmethod from dataclasses import dataclass from ruamel.yaml import YAML, yaml_object yaml = YAML() @dataclass class A(ABC): some_var: str options: list[str] def __post_init__(self): self.other_var = random.choice(self.options) @yaml_object(yaml) @dataclass class B(A): yaml_tag = "!B" more: int @classmethod def from_yaml(cls, constructor, node): # 解析YAML节点为属性字典 mapping = constructor.construct_mapping(node, deep=True) # 通过构造器创建实例,自动触发__init__和__post_init__ return cls(**mapping) @yaml_object(yaml) @dataclass class C(A): yaml_tag = "!C" foo: list[int] def __post_init__(self): super().__post_init__() self.bar = random.choice(self.foo) @classmethod def from_yaml(cls, constructor, node): mapping = constructor.construct_mapping(node, deep=True) return cls(**mapping) # 测试加载 data = yaml.load("config.yml") # 验证结果 for item in data['test_classes']: print(type(item).__name__, item.other_var) if isinstance(item, C): print("C类bar值:", item.bar)
原理说明
ruamel.yaml默认处理自定义标签对象时,会用object.__new__(cls)创建空实例(跳过__init__),再逐个赋值属性。自定义from_yaml方法后,会先解析YAML节点为键值对字典,再通过cls(**mapping)调用dataclass自动生成的构造器,从而触发__init__及后续的__post_init__方法,完成随机值的初始化逻辑。
内容的提问来源于stack exchange,提问作者Darkproduct
相关产品推荐
相关产品推荐

