__post_init__ Processing

RoadmapsPython

Overview

The `__post_init__` method is a special hook in dataclasses that runs immediately after the auto-generated `__init__` finishes. It allows you to execute custom logic before the caller receives the new instance. Mental model: `__init__` puts the raw data into the box, and `__post_init__` inspects or modifies the box before handing it back. Common use cases for this hook include: - Validating data to ensure it meets constraints, raising `ValueError` if it fails - Normalizing inputs by formatting or casting them to standardized forms - Computing dependent fields whose values rely on other provided arguments To compute a field dynamically without requiring the caller to provide it, you must declare it with `field(init=False)`. @dataclass class User: username: str is_admin: bool = field(init=False) def __post_init__(self): self.is_admin = self.username.startswith("admin_") When working with frozen dataclasses, normal attribute assignment inside `__post_init__` will raise a `FrozenInstanceError`. To bypass the freeze during initialization, you must use `object.__setattr__`. @dataclass(frozen=True) class Config: path: str def __post_init__(self): normalized = self.path.lower() object.__setattr__(self, "path", normalized) This rule and its implications are covered fully in frozen-dataclasses.

Because `@dataclass` generates `__init__` for you, you can't easily add validation or compute fields inside the constructor. `__post_init__` provides a hook to perform these actions automatically every time an instance is created.

Where used: Configuration parsing, Data validation pipelines, Pydantic-like early validation

Why learn this

Code walkthrough

from dataclasses import dataclass, field

@dataclass
class User:
    username: str
    is_admin: bool = field(init=False)

    def __post_init__(self):
        self.is_admin = self.username.startswith('admin_')

u = User('admin_alice')
print(u.is_admin)

Focus: `self.is_admin = ...` calculates the dependent field after `username` is set.

Aha moment

from dataclasses import dataclass

@dataclass
class Rectangle:
    width: int
    height: int

    def __post_init__(self):
        print(f'Validating {self.width}x{self.height}')
        if self.width <= 0:
            raise ValueError('Width must be positive')

Rectangle(10, 5)

Prediction: Will the `'Validating...'` print statement execute if you create a valid `Rectangle`?

Common guess: No, or I have to call `validate()` manually.

Yes, it prints `'Validating 10x5'`. `__post_init__` is guaranteed to run automatically at the very end of instantiation, intercepting and verifying the data before the caller receives the object.

Common mistakes

Glossary

normalizing
Adjusting or changing data so it follows a standard, consistent format, like converting all text to lowercase. Example: `email.lower()`.
hook
A specific place or method provided by a system that allows you to insert your own custom code to run automatically. Example: `__post_init__`.

Recall questions

Understanding checks

What is printed when this code is executed?

0.0

The auto-generated `__init__` sets `self.price = -50`. Then `__post_init__` runs, sees the negative price, and overwrites it with `0.0`.

Why does this code fail to run as intended?

`TypeError: __init__() missing 1 required positional argument: 'area'`

Because `area` is defined as a normal field, the auto-generated `__init__` requires it to be passed. To compute it in `__post_init__` without requiring it, it should be defined with `field(init=False)`.

What happens when you try to instantiate `Point(-5, 10)`?

It raises a `FrozenInstanceError`.

Because `Point` is a frozen dataclass, normal attribute assignment (like `self.x = 0`) fails, even inside `__post_init__`. You must use `object.__setattr__(self, 'x', 0)` to modify fields during initialization.

Practice tasks

Normalize the Username

Given this dataclass, modify it by adding a `__post_init__` method that normalizes `username` to be entirely lowercase.

Challenge

Data Validation on Init

Create a dataclass `Temperature` that takes one float field `celsius`. Implement `__post_init__` to raise a `ValueError` if `celsius` is below absolute zero (-273.15).

Return to Python Roadmap