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

如何实现支持嵌套类继承的类注解父类(类似dataclass)并将数据存储到字典中

如何实现支持嵌套类继承的类注解父类(类似dataclass)并将数据存储到字典中

我明白你想要打造一个类似dataclass但支持多层继承,还把所有数据都存在字典里的父类。你当前的代码只能处理直接子类,嵌套继承就失效了——核心问题在于你只收集了当前子类的类型提示和默认值,完全没考虑父类层面的属性。咱们来把这个问题彻底解决:

问题根源分析

你的原代码在__init_subclass__里用get_type_hints(cls)只获取了当前类的属性提示,defaults也只从当前类的__dict__里提取。当定义NestedData这样的嵌套子类时,父类MyData的x、y、label这些属性根本没被纳入处理逻辑,初始化时自然会出问题。

修复后的完整代码

我们需要遍历类的方法解析顺序(MRO),递归收集所有父类(直到Data本身)的属性信息,合并后再用于初始化逻辑:

from typing import get_type_hints, FrozenDict

class Data:
    metadata: dict

    def __init_subclass__(cls):
        # 收集所有父类(排除Data本身)的类型提示和默认值
        all_hints = {}
        all_defaults = {}
        
        # 遍历MRO,从最上层父类到当前类,确保父类属性先被加载,子类可以覆盖
        for base in reversed(cls.__mro__):
            if base is Data:
                break
            # 合并父类的类型提示
            base_hints = get_type_hints(base)
            all_hints.update(base_hints)
            # 合并父类的默认值
            base_defaults = {k: v for k, v in base.__dict__.items() if not k.startswith("__")}
            all_defaults.update(base_defaults)
        
        # 最后合并当前类的属性,确保子类属性覆盖父类同名属性
        current_hints = get_type_hints(cls)
        all_hints.update(current_hints)
        current_defaults = {k: v for k, v in cls.__dict__.items() if not k.startswith("__")}
        all_defaults.update(current_defaults)

        def __init__(self, metadata=FrozenDict(), **kwargs):
            metadata = dict(metadata)
            # 处理所有收集到的属性
            for key, typ in all_hints.items():
                if key == "metadata":
                    continue
                if key in kwargs:
                    metadata[key] = kwargs[key]
                elif key in all_defaults:
                    metadata[key] = all_defaults[key]
                else:
                    raise TypeError(f"Missing required argument: '{key}'")

            self.metadata = metadata

        cls.__init__ = __init__

        def __repr__(self):
            if not hasattr(self, "__cached_repr"):
                self.__cached_repr = f"{cls.__name__}(metadata={repr(self.metadata)})"
            return self.__cached_repr

        cls.__repr__ = __repr__

        def __str__(self):
            if not hasattr(self, "__cached_str"):
                argstr = ",".join(
                    f"{k}={repr(v)}" for k, v in self.metadata.items() if k in all_hints
                )
                self.__cached_str = f"{cls.__name__}({argstr})"
            return self.__cached_str

        cls.__str__ = __str__

测试验证

现在你的示例代码就能正常运行了:

class MyData(Data):
    x: int
    y: float = 3.14  # 默认值
    label: str = "default"

class NestedData(MyData):
    extra: str = "nested"

# ✅ 直接子类正常工作
n1 = MyData(x=10)
print(n1)  # 输出: MyData(x=10,y=3.14,label='default')
print(n1.metadata)  # 输出: {'x': 10, 'y': 3.14, 'label': 'default'}

# ✅ 嵌套子类也正常工作了
n2 = NestedData(x=10)
print(n2)  # 输出: NestedData(x=10,y=3.14,label='default',extra='nested')
print(n2.metadata)  # 输出: {'x': 10, 'y': 3.14, 'label': 'default', 'extra': 'nested'}

额外说明

  • 属性覆盖:如果子类定义了和父类同名的属性,子类的属性会优先生效(因为我们最后合并当前类的属性)
  • 性能优化:__init_subclass__只会在类定义时运行一次,所有属性收集逻辑都是一次性完成的,不会影响实例初始化的性能
  • 默认值处理:如果不需要FrozenDict,可以把默认的metadata参数换成普通dict(),注意要避免可变默认参数的陷阱(当前用FrozenDict是安全的)

备注:内容来源于stack exchange,提问作者Nick Skywalker

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.14 09:33:03