结构JSON到一个' attrs '类与额外的字段使用' cattrs ' ?



我想将JSON结构为允许使用cattrs的额外字段的attrs类。cattrs默认将忽略额外的字段,如果forbid_extra_keys=True传递额外的字段时将引发错误。

我想做相反的事情:通过允许额外的字段来改变默认行为。我创建了一个attrs类来这样做,但我有点不确定如何继续使用自定义cattrs转换器。以下是目前为止的内容:

import attr
from cattr.preconf.json import make_converter

@attr.s(auto_detect=True)
class ClassWithExtras:
foo: int
def __init__(self, **attributes) -> None:
for field, value in attributes.items():
if field in self.__attrs_attrs__:
self.__attrs_init__(field=value)
else:
setattr(self, field, value)
converter = make_converter()
converter.register_structure_hook_func(
lambda cls: issubclass(cls, ClassWithExtras), lambda attribs, cls: cls(**attribs)
)
structured = converter.structure({"foo": "2", "bar": 5}, ClassWithExtras)

我的问题是,因为我们基本上只是在类中解包字典,类型是不正确的。例如:执行类似structured.foo + structured.bar的操作将引发一个错误,即我们无法连接/sumstrint

cattrs/attrs中有办法做到这一点吗?

你想做的有点不明智;attrs类的全部意义在于提前枚举所有字段。如果您在实例上粘贴任意属性,则必须使用非槽类,您的辅助函数(如__repr____eq__)将无法正常工作(额外的属性将被忽略),并且正如您正确得出的结论,cattrs无法帮助您进行类型转换(因为它实际上没有地方找到类型)。

也就是说,我已经重写了您的示例,将逻辑从类移到转换器中,我发现这样更优雅。

from typing import Any
from attr import define, fields
from cattr.gen import make_dict_structure_fn
from cattr.preconf.json import make_converter

@define(slots=False)
class ClassWithExtras:
foo: int

converter = make_converter()

def make_structure(cl):
# First we generate what cattrs would have used by default.
default_structure = make_dict_structure_fn(cl, converter)
# We generate a set of known attribute names to use later.
attribute_names = {a.name for a in fields(cl)}
# Now we wrap this in a function of our own making.
def structure(val: dict[str, Any], _):
res = default_structure(val)
# `res` is an instance of `cl` now, so we just stick
# the missing attributes on it now.
for k in val.keys() - attribute_names:
setattr(res, k, val[k])
return res
return structure

converter.register_structure_hook_factory(
lambda cls: issubclass(cls, ClassWithExtras), make_structure
)
structured = converter.structure({"foo": "2", "bar": 5}, ClassWithExtras)
assert structured.foo == 2
assert structured.bar == 5

这基本上做了你的例子所做的,只是使用了cattrs而不是attrs。

现在,我也有一个反建议。假设我们没有将额外的属性直接粘贴到类上,而是将它们收集到一个字典中,并将该字典粘贴到常规字段中。下面是整个示例,重写为:

from typing import Any
from attr import define, fields
from cattr.gen import make_dict_structure_fn
from cattr.preconf.json import make_converter

@define
class ClassWithExtras:
foo: int
extras: dict[str, Any]

converter = make_converter()

def make_structure(cl):
# First we generate what cattrs would have used by default.
default_structure = make_dict_structure_fn(cl, converter)
# We generate a set of known attribute names to use later.
attribute_names = {a.name for a in fields(cl)}
# Now we wrap this in a function of our own making.
def structure(val: dict[str, Any], _):
val["extras"] = {k: val[k] for k in val.keys() - attribute_names}
res = default_structure(val)
return res
return structure

converter.register_structure_hook_factory(
lambda cls: issubclass(cls, ClassWithExtras), make_structure
)
structured = converter.structure({"foo": "2", "bar": 5}, ClassWithExtras)
assert structured.foo == 2
assert structured.extras["bar"] == 5
assert structured == ClassWithExtras(2, {"bar": 5})

相关内容

  • 没有找到相关文章

最新更新