当 ruamel.yaml 从字符串加载 @dataclass 时,不会调用 __post_init__

2024-01-11

假设我创建了一个@dataclass class Foo,并添加了一个__post_init__执行类型检查和处理。

当我尝试yaml.load a !Foo目的,__post_init__不被调用。

from dataclasses import dataclass, fields

from ruamel.yaml import yaml_object, YAML


yaml = YAML()


@yaml_object(yaml)
@dataclass
class Foo:
    foo: int
    bar: int

    def __post_init__(self):
        raise Exception
        for field in fields(self):
            value = getattr(self, field.name)
            typ = field.type
            if not isinstance(value, typ):
                raise Exception

s = '''\
!Foo
foo: "foo"
bar: "bar"
'''
yaml.load(s)

通过 ruamel.yaml 加载数据类时如何执行参数检查?

此行为出现在 Python 3.7 和 3.6 中pip install dataclasses.


现在 0.17.34 已支持此功能,您可以在其中执行以下操作

from dataclasses import dataclass
from ruamel.yaml import YAML

yaml = ruamel.yaml.YAML()

@yaml.register_class
@dataclass
class Foo:
   ....

之所以__post_init__不叫,是因为ruamel.yaml(以及其中的 PyYAML 代码Constructors),很久以前就创建了dataclasses被创建。

当然,用于拨打电话的代码__post_init_()可以添加到ruamel.yaml的Python对象构造函数,最好是在测试是否使用创建了某些东西之后@dataclass,否则是一个非数据类类,它恰好有一个名为的方法__post_init_,会在加载过程中突然调用该方法。

如果您没有这样的类,您可以将您自己的、更智能的构造函数添加到YAML()第一次加载/转储之前的实例(此时构造函数被实例化)使用yaml.Constructor = MyConstructor。但是添加构造函数并不像子类化那么简单RoundTripConstructor,因为所有支持的节点类型都需要注册到这样一个新的构造函数类型上。

大多数时候,我发现在上面修补适当的方法更容易RoundTripConstructor:

from dataclasses import dataclass, fields
from ruamel.yaml import yaml_object, YAML, RoundTripConstructor


def my_construct_yaml_object(self, node, cls):
    for data in self.org_construct_yaml_object(node, cls):
      yield data
    # not doing a try-except, in case `__post_init__` does catch the AttributeError
    post_init = getattr(data, '__post_init__', None)
    if post_init:
        post_init()

RoundTripConstructor.org_construct_yaml_object = RoundTripConstructor.construct_yaml_object
RoundTripConstructor.construct_yaml_object = my_construct_yaml_object

yaml = YAML()
yaml.preserve_quotes = True

@yaml_object(yaml)
@dataclass
class Foo:
    foo: int
    bar: int

    def __post_init__(self):
        for field in fields(self):
            value = getattr(self, field.name)
            typ = field.type
            if not isinstance(value, typ):
                raise Exception

s = '''\
!Foo
foo: "foo"
bar: "bar"
'''
d = yaml.load(s)

抛出异常:

Traceback (most recent call last):
  File "try.py", line 36, in <module>
    d = yaml.load(s)
  File "/home/venv/tmp-46489abf428c4cd4/lib/python3.7/site-packages/ruamel/yaml/main.py", line 266, in load
    return constructor.get_single_data()
  File "/home/venv/tmp-46489abf428c4cd4/lib/python3.7/site-packages/ruamel/yaml/constructor.py", line 105, in get_single_data
    return self.construct_document(node)
  File "/home/venv/tmp-46489abf428c4cd4/lib/python3.7/site-packages/ruamel/yaml/constructor.py", line 115, in construct_document
    for dummy in generator:
  File "try.py", line 10, in my_construct_yaml_object
    post_init()
  File "try.py", line 29, in __post_init__
    raise Exception
Exception

请注意,YAML 中的双引号是多余的,因此如果您想在往返中保留这些双引号,则需要执行以下操作yaml.preserve_quotes = True

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

当 ruamel.yaml 从字符串加载 @dataclass 时,不会调用 __post_init__ 的相关文章

随机推荐