这是一个具有与dataclasses
模块本身的复杂度相匹配的复杂度的请求:这意味着,实现此“嵌套字段”功能的最佳方法可能是定义一个类似于的新装饰器@dataclass
。
值得一提的是,如果不需要该__init__
方法的签名来反映字段及其默认值,例如通过调用渲染的类dataclass
,则可以简单得多:类装饰器将调用原始装饰器dataclass
并为其包装一些功能生成的__init__
方法可以使用简单的...(*args, **kwargs):
样式功能来实现。
换句话说,要做的就是对生成的__init__
方法进行包装,该方法将检查在“ kwargs”中传递的参数,检查是否有任何对应于“数据类字段类型”的参数,如果是,则在调用之前生成嵌套对象原来的__init__
。也许用英语比用Python拼写更难:
from dataclasses import dataclass, is_dataclass
def 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'