How to set the value of dataclass field in __post_init__ when frozen=True?
Use the same thing the generated __init__
method does: object.__setattr__
.
def __post_init__(self):
object.__setattr__(self, 'value', RANKS.index(self.rank) + 1)
Using mutation
Frozen objects should not be changed. But once in a while the need may arise. The accepted answer works perfectly for that. Here is another way of approaching this: return a new instance with the changed values. This may be overkill for some cases, but it's an option.
from copy import deepcopy
@dataclass(frozen=True)
class A:
a: str = ''
b: int = 0
def mutate(self, **options):
new_config = deepcopy(self.__dict__)
# some validation here
new_config.update(options)
return self.__class__(**new_config)
Another approach
If you want to set all or many of the values, you can call __init__
again inside __post_init__
. Though there are not many use cases.
The following example is not practical, only for demonstrating the possibility.
from dataclasses import dataclass, InitVar
@dataclass(frozen=True)
class A:
a: str = ''
b: int = 0
config: InitVar[dict] = None
def __post_init__(self, config: dict):
if config:
self.__init__(**config)
The following call
A(config={'a':'a', 'b':1})
will yield
A(a='a', b=1)
without throwing error. This is tested on python 3.7 and 3.9.
Of course, you can directly construct using A(a='hi', b=1)
, but there maybe other uses, e.g. loading configs from a json file.
Bonus: an even crazier usage
A(config={'a':'a', 'b':1, 'config':{'a':'b'}})
will yield
A(a='b', b=1)