Python dataclass继承层级添加带默认值属性的问题及解决方案
给定如下Python dataclass继承层级:
@dataclass class A: a: str aa: str @dataclass class B(A): b: str @dataclass class C(A): c: str @dataclass class D(B, C): d: str
是否存在通用方法,可以在该继承层级中插入带默认值的属性?例如如下写法会为类D触发Non-default argument(s) follows default argument(s) defined in 'B'错误:
@dataclass class A: a: str aa: str @dataclass class B(A): b: str = 'b' # <-- 新增默认值 @dataclass class C(A): c: str @dataclass class D(B, C): d: str
现有两种常见解决方案均存在明显缺陷:
- 为层级中所有属性设置
None默认值,再添加__post_init__()校验逻辑,若属性为None则抛出异常。该方案不够优雅,且需要在层级的每个dataclass中重复实现校验逻辑。 - 拆分继承分支,分别创建带默认值和不带默认值的两个独立继承分支。该方案的缺点是会快速生成大量类,导致继承层级臃肿。
补充更新
笔者自行实现了一个meta_dataclass装饰器,通过封装处理解决了上述问题,虽然可能存在部分规范冲突,但欢迎大家优化改进。
该装饰器允许在meta_dataclass继承层级的任意位置添加带默认值的属性,设计思路参考上述双分支继承方案,区别是将额外类的创建逻辑封装在meta_dataclass装饰器内部,无需开发者手动维护双分支。当前缺点是meta_dataclass只能继承自其他meta_dataclass。
完整实现代码:
from dataclasses import make_dataclass, MISSING, field class Dataclass: pass class Declarations: pass class Definitions: pass def meta_dataclass(cls=None, /, *, init=True, repr=True, eq=True, order=False, unsafe_hash=False, frozen=False): def wrap(cls): declaration_bases = [] definition_bases = [] for base in cls.__bases__: if issubclass(base, Dataclass): declaration_bases += [c for c in base.__bases__ if issubclass(c, Declarations)] definition_bases += [c for c in base.__bases__ if issubclass(c, Definitions)] elif len(cls.__bases__) == 1 and base != object: raise ValueError(f'meta_dataclasses can only inherit from other meta_dataclasses. 'f'{cls} inherits from {base}') declaration_bases.append(Declarations) definition_bases.append(Definitions) fields = [] if hasattr(cls, '__annotations__'): for field_name, field_type in cls.__annotations__.items(): f = field(default=cls.__dict__[field_name]) if field_name in cls.__dict__ else field() fields.append((field_name, field_type, f)) declarations = make_dataclass(cls_name=f'{cls.__name__}_Declarations', bases=tuple(declaration_bases), fields=[f for f in fields if isinstance(f[2].default, type(MISSING))], init=init, repr=repr, eq=eq, order=order, unsafe_hash=unsafe_hash, frozen=frozen) definitions = make_dataclass(cls_name=f'{cls.__name__}_Definitions', bases=tuple(definition_bases), fields=[f for f in fields if not isinstance(f[2].default, type(MISSING))], init=init, repr=repr, eq=eq, order=order, unsafe_hash=unsafe_hash, frozen=frozen) cls_wrapper = make_dataclass(cls_name=cls.__name__, fields=[], bases=(Dataclass, definitions, declarations), namespace=cls.__dict__, init=init, repr=repr, eq=eq, order=order, unsafe_hash=unsafe_hash, frozen=frozen) return cls_wrapper if cls is None: return wrap else: return wrap(cls)
使用示例:
@meta_dataclass class A: a: str aa: str @meta_dataclass class B(A): b: str = 'b' @meta_dataclass class C(A): c: str @meta_dataclass class D(B, C): d: str >>> help(D) Help on class D in module types: class D(__main__.Dataclass, D_Definitions, D_Declarations) | D(a: str, aa: str, c: str, d: str, b: str = 'b') -> None | | D(a: str, aa: str, c: str, d: str, b: str = 'b') | | Method resolution order: | D | __main__.Dataclass | D_Definitions | B_Definitions | C_Definitions | A_Definitions | __main__.Definitions | D_Declarations | B_Declarations | C_Declarations | A_Declarations | __main__.Declarations | builtins.object
实现逻辑示意图:
下图展示了@meta_dataclass装饰器对单个类的处理逻辑:
内容的提问来源于stack exchange,提问作者rindis
相关产品推荐
相关产品推荐

