I have a parent class, that contains a child class. Both are implemented with python dataclasses. The classes look like this:
from __future__ import annotations
from dataclasses import dataclass
@dataclass
class Parent:
name: str
child: Child
@dataclass
class Child:
parent: Parent
The goal is, to access the child class from the parent class, but also the parent class from the child class. At the same time I don't want to have to annotate either of the references as Optional.
Since the child object only exist with a parent object, this would possible:
from __future__ import annotations
from dataclasses import dataclass
@dataclass
class Parent:
name: str
child: Child
def __post_init__(self):
self.child.parent = self
@dataclass
class Child:
parent: Parent = None
Parent(name="foo", child=Child())
However, since I am using mypy, it complains that Child.parent
should be annotated with Optional[Parent]
. In practice this is only true until after the __post_init__
call. How could I get around this issue?
Python doesn't have the concept of an "unitialized variable" - it either exists, and is defined with some value, or not.
If you want to get the nefits of dataclass for the .parent
attribute, it has to exist, even for brief moments, with None
- and therefore None
must be set as an allowed value for static type analysis purposes.
There is no "workaround" that - it is how it should be. You can write Parent | None
instead of Optional[Parent]
- for the tooling it is just the same, but semantically it could be better.
Ok - maybe there is a workaround: you might have a special value "Parent" - a kind of "Parent singleton" meaning the parent had not yet been set, and use that as the default value. But chances are this is just overengineering to satisfy the tooling, not the problem you have at hand.
@dataclass
class Parent:
name: str
child: Child
def __post_init__(self):
self.child.parent = self
# make this a valid 'Parent' but
# overrides the fields that won't
# make sense in a "null parent"
class _NoParentSet(Parent):
name: str = ""
child: None = None
def __post_init__(self):
pass
# create a single instance of that class:
NoParentSet = _NoParentSet()
@dataclass
class Child:
parent: Parent = NoParentSet