jim*_*qaz 5 python yaml ruamel.yaml
假设我创建了一个@dataclass class Foo,并添加了一个__post_init__来执行类型检查和处理。
当我尝试yaml.load一个!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)
Run Code Online (Sandbox Code Playgroud)
通过 ruamel.yaml 加载数据类时如何执行参数检查?
此行为发生在 Python 3.7 以及带有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:
....
Run Code Online (Sandbox Code Playgroud)
之所以__post_init__不被调用,是因为ruamel.yaml(及其中的 PyYAML 代码Constructor)是在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)
Run Code Online (Sandbox Code Playgroud)
抛出异常:
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
Run Code Online (Sandbox Code Playgroud)
请注意,YAML 中的双引号是多余的,因此如果您想在往返中保留这些双引号,则需要执行以下操作yaml.preserve_quotes = True
我不完全确定这是否是正确的解决方法......
我可以将逻辑从 移动__post_init__到__setstate__(state: dict),由 调用YAML().load()。
def __setstate__(self, state):
self.__dict__.update(state)
# I could call self.__post_init__(), or alternatively move logic here:
for field in fields(self):
value = getattr(self, field.name)
typ = field.type
if not isinstance(value, typ):
raise Exception
Run Code Online (Sandbox Code Playgroud)
YAML().load(s)Foo.__setstate__(state)如果该方法存在,则调用,但显然不存在__init__(调用__post_init__)。这是一个有意的设计决定吗?