当ruamel.yaml从字符串加载@dataclass时,不调用__post_init__ [英] When ruamel.yaml loads @dataclass from string, __post_init__ is not called
问题描述
假设我创建了一个@dataclass class Foo
,并添加了一个__post_init__
来执行类型检查和处理.
Assume I created a @dataclass class Foo
, and added a __post_init__
to perform type checking and processing.
当我尝试yaml.load
一个!Foo
对象时,未调用__post_init__
.
When I attempt to yaml.load
a !Foo
object, __post_init__
is not called.
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加载数据类时如何执行参数检查?
How do I perform parameter checking when loading dataclasses via ruamel.yaml?
此行为在Python 3.7以及pip install dataclasses
的3.6中发生.
This behavior occurs in Python 3.7 as well as 3.6 with pip install dataclasses
.
推荐答案
之所以未调用__post_init__
的原因是,ruamel.yaml
(及其Constructor
中的PyYAML代码)早于dataclasses
已创建.
The reason why __post_init__
is not called, is because ruamel.yaml
(and the PyYAML code in its Constructor
s), was created long before dataclasses
was created.
当然,可以将调用__post_init_()
的代码添加到ruamel.yaml
的Python对象构造函数中,最好在测试是否使用@dataclass
创建了某些东西之后,否则将使用非Data-Class类,碰巧有一个名为__post_init_
的方法,突然在加载过程中会调用该方法.
Of course code for making a call to __post_init_()
could be added to ruamel.yaml
's Python object constructors, preferably after a test if something was created using @dataclass
, as otherwise a non Data-Class class, that happens to have such a method named __post_init_
, will all of a sudden have that method called during loading.
如果没有此类,则可以在使用yaml.Constructor = MyConstructor
首次加载/转储(实例化构造函数的那一刻)之前,将自己的更聪明的构造函数添加到YAML()
实例.但是添加构造函数并不像RoundTripConstructor
的子类那么简单,因为所有支持的节点类型都需要在这种新的构造函数类型上注册.
If you have no such classes, you can add your own, smarter, constructor to the YAML()
instance before first loading/dumping (at which moment the constructor is instantiated) using yaml.Constructor = MyConstructor
. But adding a constructor is not as trivial as subclassing the RoundTripConstructor
, because all supported node types need to be registered on such a new constructor type.
大多数时候,我发现在RoundTripConstructor
上修补适当的方法会更容易:
Most of the time I find it easier to just patch the appropriate method on the 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
Please note that the double quotes in your YAML are superfluous, so if you want to preserve these on round-trip you need to do yaml.preserve_quotes = True
这篇关于当ruamel.yaml从字符串加载@dataclass时,不调用__post_init__的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!