这是一个复杂度与dataclasses模块本身的复杂性相匹配的请求:这意味着实现这种“嵌套字段”功能的最佳方法可能是定义一个新的装饰器,类似于@dataclass。
幸运的是,如果一个人不需要__init__方法的签名来反映字段及其默认值,就像通过调用呈现的类一样dataclass,这可以简化得多:一个类装饰器,它将调用原始dataclass 函数并在其上包含一些功能生成的__init__方法可以使用普通的“ ...(*args, **kwargs):”样式函数。
换句话说,所有人需要做的是生成的__init__方法的包装器,它将检查在“kwargs”中传递的参数,检查是否有任何对应于“dataclass字段类型”,如果是,则在调用之前生成嵌套对象原来的__init__。也许这比用Python更难用英语拼出:
来自dataclasses import dataclass,is_dataclassdef nested_dataclass(*args, **kwargs):
def wrapper(cls):
cls = dataclass(cls, **kwargs)
original_init = cls.__init__
def __init__(self, *args, **kwargs):
for name, value in kwargs.items():
field_type = cls.__annotations__.get(name, None)
if is_dataclass(field_type) and isinstance(value, dict):
new_obj = field_type(**value)
kwargs[name] = new_obj
original_init(self, *args, **kwargs)
cls.__init__ = __init__
return cls
return wrapper(args[0]) if args else wrapper
请注意,除了不担心__init__签名之外,这也忽略了传递init=False- 因为无论如何它都没有意义。
(if返回行中的代码负责使用命名参数调用或直接作为装饰器,就像dataclass它本身一样)
并在交互式提示符上:In [85]: @dataclass
...: class A:
...: b: int = 0
...: c: str = ""
...:
In [86]: @dataclass
...: class A:
...: one: int = 0
...: two: str = ""
...:
...:
In [87]: @nested_dataclass
...: class B:
...: three: A
...: four: str
...:
In [88]: @nested_dataclass
...: class C:
...: five: B
...: six: str
...:
...:
In [89]: obj = C(five={"three":{"one": 23, "two":"narf"}, "four": "zort"}, six="fnord")
In [90]: obj.five.three.two
Out[90]: 'narf'
如果你想保留签名,我建议使用dataclasses模块本身的私有帮助函数来创建一个新的__init__。