6

I am trying to get my hands dirty with dataclasses in Python and what i want to do is have a computed field inside my class and also add the sort_index field to the call but would also want to make it frozen so that i cannot modify any attributes of this class after definition. Below is my code:

from dataclasses import dataclass, field

def _get_year_of_birth(age: int, current_year: int=2019):
    return current_year - age

@dataclass(order=True, frozen=True)
class Person():
    sort_index: int = field(init=False, repr=False)
    name: str
    lastname: str
    age: int
    birthyear: int = field(init=False)


    def __post_init__(self):
        self.sort_index = self.age
        self.birthyear = _get_year_of_birth(self.age)



if __name__ == "__main__":
    persons = [
    Person(name="Jack", lastname="Ryan", age=35),
    Person(name="Jason", lastname="Bourne", age=45),
    Person(name="James", lastname="Bond", age=60)
    ]

    sorted_persons = sorted(persons)
    for person in sorted_persons:
        print(f"{person.name} and {person.age} and year of birth is : {person.birthyear}")

It seems that i cannot set a custom sort field inside my class and also cannot create any attribute which is computed from other attributes since i am using frozen . When i run the above i get the below error:

Traceback (most recent call last):
  File "dataclasses_2.py", line 30, in <module>
    Person(name="Jack", lastname="Ryan", age=35),
  File "<string>", line 5, in __init__
  File "dataclasses_2.py", line 23, in __post_init__
    self.sort_index = self.age
  File "<string>", line 3, in __setattr__
dataclasses.FrozenInstanceError: cannot assign to field 'sort_index'

Is there a better way of doing this ? Please help

Subhayan Bhattacharya
  • 5,407
  • 7
  • 42
  • 60
  • See https://stackoverflow.com/q/57791679/2750819: while the answer primarily deals with `__init__`, the same applies for `__post_init__` (as in the CPython implemenation, internally `__init__` calls `__post_init__`). – Kent Shikama Dec 07 '19 at 22:38
  • This answer https://stackoverflow.com/a/54119384/8960865 is also helpful :) – Thomas Vetterli Oct 14 '20 at 12:13
  • 1
    Does this answer your question? [How to set the value of dataclass field in \_\_post\_init\_\_ when frozen=True?](https://stackoverflow.com/questions/53756788/how-to-set-the-value-of-dataclass-field-in-post-init-when-frozen-true) – mkl Mar 14 '21 at 13:09

1 Answers1

7

The problem is you are trying to set a field of a frozen object. There are two options here. First option would be to remove frozen=True from the dataclass specification. Secondly, if you still want to freeze Person instances, then you should initialize fields with method __setattr__. Therefore, your post_init method will become:

def __post_init__(self):
    object.__setattr__(self, 'sort_index', self.age)
    object.__setattr__(self, 'birthyear', _get_year_of_birth(self.age)
magomar
  • 1,038
  • 1
  • 12
  • 26
  • 3
    This answer is incorrect. `self.__setattr__` is exactly what raises the `FrozenInstanceError` in the first place. What you want is `object.__setattr__(self, ...)`, as detailed in the answer already mentioned above: stackoverflow.com/a/54119384/8960865 – mkl Mar 14 '21 at 13:08
  • Thanks for pointing out the mistake, it is fixed now. – magomar Mar 15 '21 at 09:52