You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.05 05:55:30