I'm using dataclasses to create a nested data structure, that I use to represent a complex test output.
Previously I'd been creating a hierarchy by creating multiple top-level dataclasses and then using composition:
from dataclasses import dataclass
@dataclass
class Meta:
color: str
size: float
@dataclass
class Point:
x: float
y: float
stuff: Meta
point1 = Point(x=5, y=-5, stuff=Meta(color='blue', size=20))
I was wondering if there was a way of defining the classses in a self-contained way, rather than polluting my top-level with a bunch of lower-level classes.
So above, the definition of Point dataclass contains the definition of Meta, rather than the definition being at the top level.
I wondered if it's possible to use inner (dataclass) classes with a dataclass and have things all work.
So I tried this:
rom dataclasses import dataclass
from typing import get_type_hints
@dataclass
class Point:
@dataclass
class Meta:
color: str
size: float
@dataclass
class Misc:
elemA: bool
elemB: int
x: float
y: float
meta: Meta
misc: Misc
point1 = Point(x=1, y=2,
meta=Point.Meta(color='red', size=5.5),
misc=Point.Misc(elemA=True, elemB=-100))
print("This is the point:", point1)
print(point1.x)
print(point1.y)
print(point1.meta)
print(point1.misc)
print(point1.meta.color)
print(point1.misc.elemB)
point1.misc.elemB = 99
print(point1)
print(point1.misc.elemB)
This all seems to work - the print outputs all work correctly, and the assignment to a (sub) member element works as well.
You can even support defaults for nested elements:
from dataclasses import dataclass
@dataclass
class Point:
@dataclass
class Meta:
color: str = 'red'
size: float = 10.0
x: float
y: float
meta: Meta = Meta()
pt2 = Point(x=10, y=20)
print('pt2', pt2)
...prints out red and 10.0 defaults for pt2 correctly
Is this a correct way to implement nested dataclasses?
(meaning it's just not lucky it works now, but would likely break in future? ...or it's just fugly and Not How You Do Things? ...or it's just Bad?)
...It's certainly a lot cleaner and a million times easier to understand and upport than a gazillion top-level 'mini' dataclasses being composed together.
...It's also a lot easier than trying to use marshmellow or jerry-rigging a json schema to class structure model.
...It also is very simple (which I like)
You can just use strings to annotate classes that don't exist yet:
from dataclasses import dataclass
@dataclass
class Point:
x: float
y: float
stuff: "Meta"
@dataclass
class Meta:
color: str
size: float
point1 = Point(x=5, y=-5, stuff=Meta(color='blue', size=20))
That way, you can reorder class definitions in the way that makes most sense. Static type checkers like mypy also respect this way of forward references, which are part of the initial pep on type annotation, so nothing exotic. Nesting the classes also solves the problem but is imo harder to read, since flat is better than nested.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With