Skip to main content

Advanced Dataclasses: __post_init__, frozen, field(), slots, and Inheritance

Advanced25 min6 exercises90 XP
0/6 exercises

You already know the basics of dataclasses — how to define fields, get automatic __init__ and __repr__, and set default values. Now it's time to unlock the advanced features.

Think of basic dataclasses like a pre-furnished apartment. It works great out of the box. Advanced dataclasses are like hiring an interior designer — you get custom validation, locked-down rooms, and exactly the layout you want.

In this tutorial, you'll learn five powerful techniques: validating data after creation, controlling default values, making objects immutable, adding memory-efficient slots, and building dataclass hierarchies with inheritance.

How Does __post_init__ Help You Validate Data?

The __post_init__ method runs automatically right after __init__ finishes. It's the perfect place to validate data, compute derived fields, or transform inputs.

Validating with __post_init__
Loading editor...

You can also use __post_init__ to compute values that depend on other fields. This is great for derived data you don't want to pass in manually.

Computing derived fields
Loading editor...

Notice the field(init=False) on area. This tells Python: "Don't include area in the constructor — I'll calculate it myself in __post_init__."


What Does field() Do and Why Do You Need It?

The field() function gives you fine-grained control over each field. The most common use is setting mutable default values safely.

Safe mutable defaults with default_factory
Loading editor...

The default_factory parameter takes a callable (a function with no arguments) that creates a fresh default value for each new instance. You can use list, dict, set, or any function you write.

Custom factory and repr=False
Loading editor...

The repr=False option hides a field from the printed representation. This is useful for long strings, passwords, or internal data you don't want cluttering the output.


How Do You Make a Dataclass Immutable with frozen=True?

By default, dataclass fields can be changed after creation. Adding frozen=True locks everything down. Any attempt to change a field raises a FrozenInstanceError.

Frozen dataclass
Loading editor...

Frozen dataclasses are automatically hashable, which means you can use them as dictionary keys or add them to sets. This is perfect for value objects that represent fixed data.

Frozen dataclasses as dict keys and set members
Loading editor...

What Does slots=True Do for Performance?

In Python 3.10+, you can add slots=True to your dataclass decorator. This automatically generates __slots__ for your class, reducing memory usage and speeding up attribute access.

dataclass with slots=True
Loading editor...

This is the cleanest way to use __slots__ in modern Python. You don't have to manually write the __slots__ tuple — the dataclass decorator does it for you based on your field definitions.


How Does Dataclass Inheritance Work?

Dataclasses support inheritance just like regular classes. A child dataclass gets all the fields from its parent, plus any new fields it defines.

Basic dataclass inheritance
Loading editor...

The child's __init__ includes all parent fields first, then child fields. So Pet.__init__ takes name, sound, owner, and vaccinated in that order.

Inheritance with defaults
Loading editor...

You can also use __post_init__ in child classes. If the parent has __post_init__, call super().__post_init__() to make sure both run.

__post_init__ with inheritance
Loading editor...

Practice Exercises

Validate with __post_init__
Write Code

Create a dataclass called Age with one field: years (int). Add a __post_init__ method that raises a ValueError with the message 'Age must be positive' if years is less than 0. Create an Age(25) and print it. Then use try/except to catch the error from Age(-5) and print 'Invalid age'.

Loading editor...
Safe List Default with field()
Fix the Bug

This code tries to give each Playlist its own list of songs, but it will crash because mutable defaults are not allowed. Fix it using field(default_factory=list).

Loading editor...
Predict the Output: Frozen Dataclass
Predict Output

Read the code carefully. What will be printed? Think about what happens when you try to modify a frozen dataclass and whether two identical frozen instances are equal.

Loading editor...
Computed Field with __post_init__
Write Code

Create a dataclass BMI with fields weight_kg (float) and height_m (float). Add a computed field bmi (float, not in __init__) that is calculated in __post_init__ as weight_kg / (height_m ** 2). Round the bmi to 1 decimal place using round(). Create a BMI(70, 1.75) and print the bmi value.

Loading editor...
Dataclass Inheritance
Write Code

Create a parent dataclass Employee with fields name (str) and department (str, default 'General'). Create a child dataclass Manager that inherits from Employee and adds a field team_size (int, default 5). Create a Manager('Alice') and print it.

Loading editor...
Refactor: Add Validation and Computed Fields
Refactor

Refactor this basic Product dataclass to add: (1) a __post_init__ that raises ValueError with 'Price must be positive' if price <= 0, (2) a computed field discounted_price (not in __init__) calculated as round(price * (1 - discount), 2). Create Product('Widget', 19.99, 0.15) and print its discounted_price.

Loading editor...