这是一个与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'
如果您希望保留签名,我建议您使用dataclasses
模块本身中的私有帮助函数来创建一个新的__init__
.