How to use post-init with dataclasses

How to use post-init with dataclasses#

By default a class in Python has an __init__ method that is called when the class is instantiated. This method is used to initialize the class attributes. The example below shows this in action and how a variable is set by combining two other variables.

Example class that uses __init__ method#
class Name:
    def __init__(self, first, last):
        self.first = first
        self.last = last
        self.full = f"{self.first} {self.last}"


def main():
    name = Name(first="John", last="Doe")
    print(name.full)


if __name__ == "__main__":
    main()

By upgrading a class to a dataclass, the __init__ method is automatically created and the class attributes are automatically initialized. This means that the full attribute is not set when the class is instantiated. The example below shows this in action and a common error that is raised when trying to setup the full attribute as self is not defined.

Example class without post-init#
from dataclasses import dataclass


@dataclass
class Name:
    first: str
    last: str
    full: str = f"{self.first} {self.last}"


def main():
    name = Name(first="John", last="Doe")
    print(name.full)


if __name__ == "__main__":
    main()

To fix this, the __post_init__ method can be used. This method is called after the class is instantiated and can be used to set the full attribute. The example below shows this in action. Besides dataclass also field is imported to be able to use the init=False parameter to tell the dataclass to not initialize the full attribute. Secondly the __post_init__ method is defined and the full attribute is set by combining the first and last attributes.

dataclass_post_init.py#
from dataclasses import dataclass, field


@dataclass
class Name:
    first: str
    last: str
    full: str = field(init=False)

    def __post_init__(self):
        self.full = f"{self.first} {self.last}"


def main():
    name = Name(first="John", last="Doe")
    print(name.full)


if __name__ == "__main__":
    main()

Now the full attribute is set when the class is instantiated and the output of the script is as expected for the first example:

Output#
John Doe