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



