如何实现子类继承父类dataclass的字段自省功能?
类级别的Dataclass字段自省问题解决方案
问题描述
定义父类dataclass ParentDC,让子类(如ChildDC)继承它,要求所有子类无需实例化就能访问primary_fields、secondary_fields等类级别的字段自省信息。当前代码运行后输出为空集合,不符合预期。
原代码
from dataclasses import dataclass, field, fields from typing import Optional @dataclass class ParentDC: type_map: dict[str, type] = field(init=False, metadata={"support": True}) primary_fields: set[str] = field( init=False, default_factory=set, metadata={"support": True} ) secondary_fields: set[str] = field( init=False, default_factory=set, metadata={"support": True} ) dtype_map: dict[str, type] = field( init=False, default_factory=dict, metadata={"support": True} ) def __init_subclass__(cls, type_map: dict[str, type]) -> None: print(cls.__class__.__qualname__) cls.type_map = type_map cls.primary_fields = set() cls.secondary_fields = set() field_data = fields(cls) for fdat in field_data: if not fdat.metadata.get("support", False): if fdat.metadata.get("secondary", False): cls.secondary_fields.add(fdat.name) else: cls.primary_fields.add(fdat.name) cls.dtype_map = { k: type_map[v].dtype for k, v in cls.__annotations__.items() if k in cls.primary_fields.union(cls.secondary_fields) } type_map = { "alpha": int, "beta": float, } @dataclass class ChildDC(ParentDC, type_map=type_map): alpha: Optional[str] = field( default=None, kw_only=True, metadata={"secondary": True} ) beta: str = field(kw_only=True) print(f"{ChildDC.primary_fields=}") print(f"{ChildDC.secondary_fields=}") print(f"{ChildDC.dtype_map=}")
实际输出
type ChildDC.primary_fields=set() ChildDC.secondary_fields=set() ChildDC.dtype_map={}
问题原因
你的猜测正确,__init_subclass__不是合适的执行时机。Python中@dataclass装饰器的执行顺序在__init_subclass__之后:子类继承父类时先触发父类的__init_subclass__,但此时子类还未被@dataclass处理,fields(cls)只能获取到父类的字段,子类自身的字段还未注册到dataclass系统中,因此无法获取有效信息。
解决方案
方案1:使用元类(推荐)
元类可以在类创建的最后阶段介入,此时@dataclass已完成字段处理,能正确获取所有子类字段。
修改后的代码:
from dataclasses import dataclass, field, fields from typing import Optional, Type class ParentDCMeta(type): def __new__(cls, name: str, bases: tuple[Type], namespace: dict, type_map: dict[str, type]): # 创建类实例 new_cls = super().__new__(cls, name, bases, namespace) # 处理类级别字段自省 new_cls.type_map = type_map new_cls.primary_fields = set() new_cls.secondary_fields = set() field_data = fields(new_cls) for fdat in field_data: if not fdat.metadata.get("support", False): if fdat.metadata.get("secondary", False): new_cls.secondary_fields.add(fdat.name) else: new_cls.primary_fields.add(fdat.name) # 修正dtype_map逻辑:按字段名从type_map取对应类型 new_cls.dtype_map = { k: type_map[k].__name__ # 若需numpy dtype可替换为type_map[k].dtype for k in new_cls.primary_fields.union(new_cls.secondary_fields) if k in type_map } return new_cls @dataclass class ParentDC(metaclass=ParentDCMeta): type_map: dict[str, type] = field(init=False, metadata={"support": True}) primary_fields: set[str] = field( init=False, default_factory=set, metadata={"support": True} ) secondary_fields: set[str] = field( init=False, default_factory=set, metadata={"support": True} ) dtype_map: dict[str, type] = field( init=False, default_factory=dict, metadata={"support": True} ) type_map = { "alpha": int, "beta": float, } @dataclass class ChildDC(ParentDC, type_map=type_map): alpha: Optional[str] = field( default=None, kw_only=True, metadata={"secondary": True} ) beta: str = field(kw_only=True) print(f"{ChildDC.primary_fields=}") print(f"{ChildDC.secondary_fields=}") print(f"{ChildDC.dtype_map=}")
方案2:使用子类装饰器
若不想使用元类,可自定义装饰器在@dataclass之后处理子类字段自省:
from dataclasses import dataclass, field, fields from typing import Optional, Callable, Type def process_dc_fields(type_map: dict[str, type]) -> Callable[[Type], Type]: def decorator(cls: Type) -> Type: cls.type_map = type_map cls.primary_fields = set() cls.secondary_fields = set() field_data = fields(cls) for fdat in field_data: if not fdat.metadata.get("support", False): if fdat.metadata.get("secondary", False): cls.secondary_fields.add(fdat.name) else: cls.primary_fields.add(fdat.name) cls.dtype_map = { k: type_map[k].__name__ for k in cls.primary_fields.union(cls.secondary_fields) if k in type_map } return cls return decorator @dataclass class ParentDC: type_map: dict[str, type] = field(init=False, metadata={"support": True}) primary_fields: set[str] = field( init=False, default_factory=set, metadata={"support": True} ) secondary_fields: set[str] = field( init=False, default_factory=set, metadata={"support": True} ) dtype_map: dict[str, type] = field( init=False, default_factory=dict, metadata={"support": True} ) type_map = { "alpha": int, "beta": float, } @dataclass @process_dc_fields(type_map=type_map) class ChildDC(ParentDC): alpha: Optional[str] = field( default=None, kw_only=True, metadata={"secondary": True} ) beta: str = field(kw_only=True) print(f"{ChildDC.primary_fields=}") print(f"{ChildDC.secondary_fields=}") print(f"{ChildDC.dtype_map=}")
预期输出
两种方案运行后都会得到:
ChildDC.primary_fields={'beta'} ChildDC.secondary_fields={'alpha'} ChildDC.dtype_map={'beta': 'float', 'alpha': 'int'}
注意点
- 原代码中
dtype_map逻辑存在问题:cls.__annotations__.items()中的v是注解类型(如Optional[str]),无法直接作为type_map的key,因此调整为按字段名从type_map取值,可根据实际需求修改。 - 元类方案适合统一处理所有子类,无需每个子类添加装饰器;装饰器方案更灵活,适合需要特殊处理的子类。
内容的提问来源于stack exchange,提问作者bzm3r
相关产品推荐
相关产品推荐

