Your example works as desired for recent Python versions.
However, the documentation is still completely lacking for nesting dataclasses
. If there are default parameters, the following methods also work:
from dataclasses import dataclass
@dataclass
class One:
f_one: int = 1
f_two: str = 'two'
@dataclass
class Two:
f_three: str = 'three'
f_four: One = One()
# nested class instance with default parameters
example = Two()
example
# nested class instance with different parameters
example = Two(f_three='four', f_four=One(f_one=2, f_two='three'))
example
# same but using dict unpacking
example = Two(**{'f_three': 'five', 'f_four': One(**{'f_one': 3, 'f_two': 'four'})})
example
# or, by changing the class initialization method to ingest a vanilla dict:
@dataclass
class Two:
f_three: str = '3'
f_four: One = One()
def __init__(self, d: dict):
self.f_three = d.get('f_three')
self.f_four = One(**d.get('f_four'))
d = {'f_three': 'six', 'f_four': {'f_one': 4, 'f_two': 'five'}}
example = Two(d)
example
The important thing here is that the class member pointing to the nested dataclass
should have the type of the dataclass
and be initialized with its values. You can nest together as many levels of dataclasses
as you like this way.
Another way is to simply use a dict
, which is easily serialized/deserialized to/from JSON:
# dict is all you need
example = {
'three': '3',
'four': {
'one': 1,
'two': '2',
}
}
An old hack borrowed from Kaggle is to unpack a nested list
or dict
into a Struct
, which is not a dataclass
, for dot access:
class Struct(dict):
"""Dataclass structure that inherits from dict."""
def __init__(self, **entries):
entries = {k: v for k, v in entries.items() if k != 'items'}
dict.__init__(self, entries)
self.__dict__.update(entries)
def __setattr__(self, attr, value):
self.__dict__[attr] = value
self[attr] = value
def structify(obj: Union[list,dict]) -> Struct:
"""Unpack list or dict into Struct for dot access of members."""
if isinstance(obj, list):
return [structify(obj[i]) for i in range(len(obj))]
elif isinstance(obj, dict):
return Struct(**{k: structify(v) for k, v in obj.items()})
return obj # else return input object
s = structify(example)
s
s.three
s.four.one
s.four.two
You could also create a TypedDict, but why combine the worst aspects of dictionaries and classes? There should be no need for an external library for such a basic thing provided by every other language. You would expect nested dataclasses to behave like nested C/C++ structs, but it is very different. Otherwise, pydantic has a nice interface for typed classes generated from unpacked dictionaries. Overall, Julia has better methods for dealing with parameter data structures in the @kwdef
macro:
@kwdef struct Foo
a::Int = 1 # default value
b::String # required keyword
end
Foo(b="hi")
obj
.Two(**obj)
gives meTwo(f_three='three', f_four=One(f_one=1, f_two='two'))
– Prowlera=Two(f_three='three', f_four=One(f_one=1, f_two='two')); print(a)
– Lavoie