【问题标题】:Structure JSON to an `attrs` class with extra fields using `cattrs`?使用 `cattrs` 将 JSON 构造为具有额外字段的`attrs` 类?
【发布时间】:2021-12-15 06:06:45
【问题描述】:

我想将 JSON 构造为一个 attrs 类,该类允许使用 cattrs 的额外字段。 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 之类的操作会引发我们无法连接/求和strint 的错误。

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

【问题讨论】:

    标签: json python-3.x python-attrs


    【解决方案1】:

    您尝试做的事情有点不明智; 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})
    

    【讨论】:

      猜你喜欢
      • 2019-05-24
      • 2021-12-24
      • 1970-01-01
      • 1970-01-01
      • 2018-07-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多