I still cannot modify an attribute. This still fails:
child.a = 1
But I can create one!
child.d = 3
assert child.d == 3
If I want Child to behave like Base, I have to decorate it again. This fails as expected:
@dataclass(frozen=True)
class RestrictedChild(Base):
c: int = 0
restricted_child = RestrictedChild()
restricted_child.d = 4
What’s confusing is the mixed behaviour of child.
Further note, child params say it is frozen: child.__dataclass_params__=_DataclassParams(init=True,repr=True,eq=True,order=False,unsafe_hash=False,frozen=True)
dataclasses tries it’s best to only affect classes it is directly applied to, which in this case means only freezing attributes that are directly part of Base. Neither child.c nor child.any_other_attribute is being write protected.
Child.c is created for Child, thus it works, whilst Child.a and Child.b are inherited from Base. Bases __setattr__ method is most likely hard coded for a and b then (unless Child were to redefine them) whilst c is not hard coded.
from dataclasses import dataclass
@dataclass(frozen=True)
class Base:
a: int = 0
b: int = 0
class Child(Base):
b: int = 0
c: int = 0
child = Child()
child.a = 1 # Error, inherited from Base
child.b = 1 # Defined in Child
child.c = 1 # Same here
The issue with child.__dataclass_params__ most likely stems from inheritance of Base being broken in that way (I could imagine it is a bug).
Since you didn’t decorate Child with dataclass, c is a class attribute, not an instance attribute. a and b use __setattr__ generated by the parent dataclass and that’s why you see the error. It is not the case for the class attribute c (i.e. you can change it by Child.c = value) or any other instance attribute you later add (this applies to assignments like child.c = value as well – you are creating a new instance attribute that shadows the class atrribute).
This is intentional behaviour for dataclass inheritance. Classes inheriting from a frozen dataclass are not automatically frozen.
Here’s the code that generates the __setattr__ and __delattr__ methods for frozen dataclasses:
If you look at the generated methods you can see that there’s a check for type(self) is cls which is True for your Base and RestrictedChild classes as they are decorated with @dataclass(frozen=True).
For the Child class, ‘a’ and ‘b’ are inherited fields, so are protected by the or name in ... condition, but as the method was only generated for Base the first condition is False and you can still create, delete and modify ‘new’ attributes.