【问题标题】:__post_init__ of python 3.x dataclasses is not called when loaded from yaml从 yaml 加载时不调用 python 3.x 数据类的 __post_init__
【发布时间】:2020-05-10 21:28:45
【问题描述】:

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

检查以下代码。在第 3 步中,我们从 yaml 字符串加载 dataclass A。注意它不会调用__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__,就像下面的代码 sn-p 一样。

a_.__post_init__()

我不确定这是否是对yaml 序列化dataclass 的安全再现。另外,当dataclass字段为dataclasses.InitVar类型时,__post_init__取kwargs也会带来问题。

【问题讨论】:

  • 如果你加载的东西没有实现 __post_init__() 怎么办?我能问你为什么使用 YAML 来保存对象而不是 pickle 吗?
  • 如果我不实现 __post_init__() 一切正常,但我希望在加载数据类时做一些事情。我使用 yaml 而不是 pickle 我想在未来使用干净的 yaml 接口构建 rest API。

标签: python-3.x yaml pyyaml python-dataclasses


【解决方案1】:

此行为按预期工作。您正在转储现有对象,因此当您加载它时 pyyaml 有意避免再次初始化该对象。即使转储对象的直接属性是在__post_init__ 中创建的,它们也会被保存,因为该函数在转储之前运行。当您想要来自 __post_init__ 的副作用时,例如示例中的 print 语句,您需要确保发生初始化。

实现这一点的方法很少。您可以使用 pyyaml's documentation 中描述的元类或添加构造函数/表示器方法。您还可以手动将示例中的转储字符串更改为 ''!!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_))

【讨论】:

    猜你喜欢
    • 2019-01-02
    • 2015-08-06
    • 2019-01-15
    • 1970-01-01
    • 2017-04-26
    • 2021-09-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多