从 yaml 加载时,不会调用 Python 3.x 数据类的__post_init__



请注意,我已经在这里提到了StackOverflow问题。我发布这个问题是为了调查打电话__post_init__是否安全。请检查问题直到最后。

检查下面的代码。在步骤 3 中,我们从字符串加载dataclassAyaml。请注意,它不会调用__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_))

最新更新