如何结合default_factory从YAML创建Python数据类?
解决dataclass结合pyyaml使用
field(default_factory)时默认值不生效的问题 当使用Python dataclass继承yaml.YAMLObject加载YAML内容时,普通默认值(如age: int = 0)可正常生效,但用field(default_factory=...)定义的属性(比如动态生成的空列表),在YAML未提供该属性时会触发AttributeError,提示对象缺少对应属性。
示例报错代码:
from dataclasses import dataclass, field import yaml @dataclass class Person2(yaml.YAMLObject): yaml_loader = yaml.SafeLoader yaml_tag = "!person2" name: str parents: list[str] = field(default_factory=list) age: int = 0 p2 = yaml.safe_load(""" !person2 name: Jacques Toto age: 100 """) print(p2)
报错信息:
AttributeError: 'Person2' object has no attribute 'parents'
原因分析
yaml.YAMLObject的默认实例构造逻辑是:先创建类的空实例(不调用__init__),再将YAML解析得到的键值对直接赋值给实例属性。而dataclass中field(default_factory=...)的默认值是在__init__方法中通过调用工厂函数生成的,YAMLObject的构造逻辑跳过了__init__,因此未在YAML中定义的属性不会被初始化,导致缺失。
解决方案
核心思路是让pyyaml在创建实例时调用dataclass的__init__方法,触发默认值的生成逻辑,以下是两种常用实现方式:
方法1:重写from_yaml类方法
在dataclass中重写from_yaml方法,手动解析YAML数据后调用类的构造函数:
from dataclasses import dataclass, field import yaml @dataclass class Person2(yaml.YAMLObject): """A Person, to be loaded from YAML""" yaml_loader = yaml.SafeLoader yaml_tag = "!person2" name: str parents: list[str] = field(default_factory=list) age: int = 0 @classmethod def from_yaml(cls, loader, node): # 解析YAML节点为字典 data = loader.construct_mapping(node, deep=True) # 调用类的__init__,自动处理所有dataclass默认值 return cls(**data)
测试验证:
p2 = yaml.safe_load(""" !person2 name: Jacques Toto age: 100 """) print(p2) # 输出:Person2(name='Jacques Toto', parents=[], age=100)
方法2:手动注册YAML构造器
不使用yaml.YAMLObject继承,直接给pyyaml注册自定义构造器,控制实例创建过程:
from dataclasses import dataclass, field import yaml @dataclass class Person2: """A Person, to be loaded from YAML""" name: str parents: list[str] = field(default_factory=list) age: int = 0 def construct_person2(loader, node): # 解析YAML数据为字典 data = loader.construct_mapping(node, deep=True) # 调用dataclass构造函数生成实例 return Person2(**data) # 给SafeLoader注册构造器 yaml.SafeLoader.add_constructor("!person2", construct_person2)
测试验证:
p2 = yaml.safe_load(""" !person2 name: Jacques Toto age: 100 """) print(p2) # 输出:Person2(name='Jacques Toto', parents=[], age=100)
总结
两种方法都通过调用dataclass的__init__方法创建实例,确保field(default_factory=...)定义的默认值被正确初始化,避免属性缺失的错误。
内容的提问来源于stack exchange,提问作者Jean-Francois T.
相关产品推荐
相关产品推荐

