请注意,我已经在这里提到了StackOverflow问题。我发布这个问题是为了调查打电话__post_init__
是否安全。请检查问题直到最后。
检查下面的代码。在步骤 3 中,我们从字符串加载dataclass
A
yaml
。请注意,它不会调用__post_init__
方法。
import dataclasses
import yaml
@dataclasses.dataclass
class A:
a: int = 55
def __post_init__(self):
print("__post_init__ got called", self)
print("n>>>>>>>>>>>> 1: create dataclass object")
a = A(33)
print(a) # print dataclass
print(dataclasses.fields(a))
print("n>>>>>>>>>>>> 2: dump to yaml")
s = yaml.dump(a)
print(s) # print yaml repr
print("n>>>>>>>>>>>> 3: create class from str")
a_ = yaml.load(s)
print(a_) # print dataclass loaded from yaml str
print(dataclasses.fields(a_))
我现在看到的解决方案是在最后自己调用__-post_init__
,如下面的代码片段所示。
a_.__post_init__()
我不确定这是否是yaml
序列化dataclass
的安全再创建.此外,当__post_init__
采用 kwargs 时,如果dataclass
字段是dataclasses.InitVar
类型,这将带来问题。
此行为按预期工作。您正在转储现有对象,因此当您加载它时,pyyaml 有意避免再次初始化该对象。转储对象的直接属性将被保存,即使它们是在__post_init__
中创建的,因为该函数在转储之前运行。当您想要来自__post_init__
的副作用时,例如示例中的 print 语句,您需要确保进行初始化。
实现此目的的方法很少。您可以使用元类或添加 pyyaml 文档中描述的构造函数/表示器方法。您也可以手动将示例中转储的字符串更改为"!!python/object/new:' 而不是 ''!!python/object:'.如果您的最终目标是以不同的方式生成 yaml 文件,那么这可能是一个解决方案。
有关使用元类方法并在从转储的类对象加载时调用__post_init__
的代码更新,请参阅下文。在from_yaml
中调用cls(**fields)
可确保对象已初始化。yaml.load
使用cls.__new__
创建标记为 ''!!python/object:',然后将保存的属性手动加载到对象中。
import dataclasses
import yaml
@dataclasses.dataclass
class A(yaml.YAMLObject):
a: int = 55
def __post_init__(self):
print("__post_init__ got called", self)
yaml_tag = '!A'
yaml_loader = yaml.SafeLoader
@classmethod
def from_yaml(cls, loader, node):
fields = loader.construct_mapping(node, deep=True)
return cls(**fields)
print("n>>>>>>>>>>>> 1: create dataclass object")
a = A(33)
print(a) # print dataclass
print(dataclasses.fields(a))
print("n>>>>>>>>>>>> 2: dump to yaml")
s = yaml.dump(a)
print(s) # print yaml repr
print("n>>>>>>>>>>>> 3: create class from str")
a_ = yaml.load(s, Loader=A.yaml_loader)
print(a_) # print dataclass loaded from yaml str
print(dataclasses.fields(a_))