cattrs.structure未调用attrs.define转换器,处理无效数据失败问题
问题:cattrs结构转换时未触发attrs字段转换器?
我尝试把包含无效数据的字典转换为@attrs.define装饰的类,预期通过attrs字段的转换器处理无效数据。直接使用Foo(**dct)实例化类时,转换器能正常处理无效数据;但使用cattrs.structure(dct, Foo)或cattrs.Converter().structure(dct, Foo)时转换失败,观察到类属性的转换器似乎未被调用,想确认这是cattrs的bug还是我的使用方式有误?
测试代码
#!/usr/bin/env python3 from __future__ import annotations from typing import Optional, Any import attrs import cattrs def converter_float_or_none(val: Any) -> Optional[float]: new_val = None try: new_val = float(val) except Exception: pass return new_val @attrs.define class Foo: xxx: Optional[float] = attrs.field(default=None, converter=converter_float_or_none) @attrs.define class Bar: foo: Foo = attrs.field(factory=Foo, converter=Foo) if __name__ == '__main__': converter = cattrs.Converter() #! --- test with valid fields --- dct_valid = {'xxx': None} dct_invalid = {'xxx': '--'} #! ok foo = Foo(**dct_valid) print(f"foo = {foo!r}") assert None == foo.xxx #! ok foo = Foo(**dct_invalid) print(f"foo = {foo!r}") assert None == foo.xxx #! ok !! foo = converter.structure(dct_valid, Foo) print(f"foo = {foo!r}") assert None == foo.xxx #! FAIL !! foo = converter.structure(dct_invalid, Foo) print(f"foo = {foo!r}") assert None == foo.xxx
运行输出
(venv_3) $ python3 xxx_test.py foo = Foo(xxx=None) foo = Foo(xxx=None) foo = Foo(xxx=None) + Exception Group Traceback (most recent call last): | File "xxx_test.py", line 54, in <module> | foo = converter.structure(dct_invalid, Foo ) | File "/home/brendansimon/venv_3/lib/python3.7/site-packages/cattrs/converters.py", line 309, in structure | return self._structure_func.dispatch(cl)(obj, cl) | File "<cattrs generated structure __main__.Foo>", line 10, in structure_Foo | if errors: raise __c_cve('While structuring ' + 'Foo', errors, __cl) | cattrs.errors.ClassValidationError: While structuring Foo (1 sub-exception) +-+---------------- 1 ---------------- | Traceback (most recent call last): | File "<cattrs generated structure __main__.Foo>", line 6, in structure_Foo | res['xxx'] = __c_structure_xxx(o['xxx'], __c_type_xxx) | File "/home/brendansimon/venv_3/lib/python3.7/site-packages/cattrs/converters.py", line 574, in _structure_optional | return self._structure_func.dispatch(other)(obj, other) | File "/home/brendansimon/venv_3/lib/python3.7/site-packages/cattrs/converters.py", line 415, in _structure_call | return cl(obj) | ValueError: could not convert string to float: '--' | Structuring class Foo @ attribute xxx +------------------------------------
解答
这不是cattrs的bug,是默认行为导致的。cattrs默认会优先根据类型注解选择结构转换逻辑,而非直接复用attrs字段定义的转换器。
在你的代码中,xxx字段的类型是Optional[float],cattrs会先尝试将输入值转换为float,转换失败则抛出异常,不会触发你定义的attrs转换器。
解决方法
方法1:让cattrs复用attrs的转换器
创建Converter实例时开启use_attrs_converters=True参数,cattrs会优先使用字段上定义的转换器:
converter = cattrs.Converter(use_attrs_converters=True)
修改后,converter.structure(dct_invalid, Foo)会调用converter_float_or_none转换器,返回None,不再抛出异常。
方法2:给cattrs注册对应类型的转换器
如果不想依赖attrs的转换器,可以为Optional[float]类型注册自定义转换逻辑:
converter.register_structure_hook(Optional[float], converter_float_or_none)
这样cattrs处理该类型时会使用你定义的转换逻辑。
注:你的Python版本为3.7,cattrs在旧版本的行为与新版本略有差异,但以上两种方法均可解决问题。
内容的提问来源于stack exchange,提问作者user19007114
相关产品推荐
相关产品推荐

