I am unsure why you'd want to write an explicit __init__
*
, although you may need to be on Python 3.10 to be able to pass in fields specific to Child
to its init. **
from dataclasses import dataclass
@dataclass(kw_only=True)
class RichParent:
many_fields: int = 1
more_fields: bool = False
#for some reason not setting `@dataclass`` again here means the built in str/repr
#skips the child-only field.
#also if you don't use `kw_only=True` on Child, can't pass in Child only fields
@dataclass(kw_only=True)
class Child(RichParent):
some_extra: bool = False
seed = RichParent(many_fields=2, more_fields=True)
print(f"\n{seed=}")
child = Child(**vars(seed))
print(f"\n{child=} with {vars(child)=} which does include more_fields")
child2 = Child(**vars(seed),some_extra=True)
print(f"\n{child2=}")
#see the behavior changes in print and field acceptable to constructor
class ChildNoDC(RichParent):
some_extra: bool = False
child_no_dcdec = ChildNoDC(**vars(seed))
print(f"\n{child_no_dcdec=} with {vars(child_no_dcdec)=} which does include more_fields")
try:
child_no_dcdec2 = ChildNoDC(some_extra=True,**vars(seed))
except (Exception,) as e:
print("\n",e, "as expected")
output:
seed=RichParent(many_fields=2, more_fields=True)
child=Child(many_fields=2, more_fields=True, some_extra=False) with vars(child)={'many_fields': 2, 'more_fields': True, 'some_extra': False} which does include more_fields
child2=Child(many_fields=2, more_fields=True, some_extra=True)
child_no_dcdec=ChildNoDC(many_fields=2, more_fields=True) with vars(child_no_dcdec)={'many_fields': 2, 'more_fields': True} which does include more_fields
RichParent.__init__() got an unexpected keyword argument 'some_extra' as expected
*
If you did need some custom init on Child, use the builtin post_init hook to do it:
def __post_init__(self):
print(f"{self} lets do stuff here besides the std data init...")
From talking about shallow copies, you know about mutability issues, but you could hack some stuff like self.my_dict = self.my_dict.copy()
hacks here to work around that.
**
as far as I understand, @dataclass does not flip the class into a dataclass type. It just builds an __init__
and some methods, (using metaclasses?). So ChildNoDC
doesn't know it is dataclass-style class and just tries to pass all fields passed into its constructor call onto Parent
's __init__
. Child
having been "told" it's dataclassed too, knows better. (That's also the reason the child-only field wasn't being printed). And kw-only
flag frees up some positioning and defaults-only constraints too.