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

如何实现子类继承父类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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.20 14:53:11