Python dataclass装饰器搭配含default_factory的field重复调用报错原因
重复调用dataclass装饰器触发default_factory相关报错的底层原因
这不是Python的bug,是dataclass装饰器处理已生成dataclass类的字段识别逻辑导致的必然现象:
- dataclass装饰器第一次运行时,会将类字段的所有元信息(包括default、default_factory配置)存储到类的私有属性
__dataclass_fields__中,但对于仅配置了default_factory的field,不会在类的公开属性中写入对应的默认值——这是符合设计的,因为default_factory的默认值是实例化时动态生成的,不应该作为类属性存在。 - 当第二次调用
dataclass()装饰同一个类时,装饰器会优先直接扫描类的公开注解和类属性来判断字段是否有默认值,不会复用已有的__dataclass_fields__信息,此时仅配置了default_factory的字段会被识别为没有默认值的普通字段。
三个复现场景的对应解释
1. 无default_factory场景
第一次装饰完成后,所有字段的默认值都作为普通类属性存在,第二次扫描时可以正常识别到a=5、b=6都有默认值,参数顺序符合Python语法要求,装饰正常完成,因此B == A输出True。
代码复现:
from dataclasses import dataclass @dataclass class A: a: int = 5 b: int = 6 B = dataclass(A) print(B == A) # 输出True
2. 含default_factory场景
第二次扫描时,a的默认值5可以被正常识别为有默认值的字段,但b由于仅配置了default_factory,类属性中没有对应的默认值,被识别为无默认值的字段,此时字段顺序是「有默认值的a在前,无默认值的b在后」,违反了Python函数参数「无默认值参数不能放在有默认值参数之后」的语法规则,因此直接抛出TypeError: non-default argument 'b' follows default argument。
代码复现:
from dataclasses import dataclass, field from typing import List @dataclass class A: a: int = 5 b: List = field(default_factory=lambda: [1, 2, 3]) B = dataclass(A) # 此处直接报错 print(B == A)
3. 调整字段顺序场景
将b放到a前面后,第二次扫描时识别到的字段顺序是「无默认值的b在前,有默认值的a在后」,符合Python参数顺序要求,因此装饰可以正常完成,B == A输出True。
代码复现:
from dataclasses import dataclass, field from typing import List @dataclass class A: b: List = field(default_factory=lambda: [1, 2, 3]) a: int = 5 B = dataclass(A) print(B == A) # 输出True
规避方案
不要对已经用@dataclass装饰过的类重复调用dataclass装饰器,如果需要基于已有dataclass生成新的dataclass,推荐使用make_dataclass函数或者继承的方式实现。
内容的提问来源于stack exchange,提问作者someabra
相关产品推荐
相关产品推荐

