Alex Lin Wang 王帅

__post_init__

Validate dataclass fields at instantiation time using __post_init__ for better encapsulation and data integrity. Learn how to enforce business rules right when objects are created.

Let's say you're building a time-based system and you want to create an Event class. However, you want to ensure that the event is only valid if the end time comes after the start time, and you want this validation to happen at instantiation time for proper encapsulation.

from datetime import datetime
from dataclasses import dataclass

@dataclass
class Event:
    name: str
    start_time: datetime
    end_time: datetime

# This creates an invalid event without any validation
event = Event(
    name="Meeting",
    start_time=datetime(2025, 9, 6, 14, 0),
    end_time=datetime(2025, 9, 6, 13, 0)  # Ends before it starts!
)

print("Event duration:", event.end_time - event.start_time)  # Negative duration!

The issue above is that we can create logically invalid objects. The event ends before it starts, which doesn't make sense in our domain. We need to validate our data at creation time to maintain data integrity and prevent bugs downstream.

The __post_init__ method is called automatically after the dataclass __init__ method completes. This is the perfect place to add validation logic that depends on multiple fields.

from datetime import datetime
from dataclasses import dataclass

@dataclass
class Event:
    name: str
    start_time: datetime
    end_time: datetime
    
    def __post_init__(self):
        if self.end_time <= self.start_time:
            raise ValueError("End time must be after start time")

# Now this will raise an error immediately
try:
    event = Event(
        name="Invalid Meeting",
        start_time=datetime(2025, 9, 6, 14, 0),
        end_time=datetime(2025, 9, 6, 13, 0)
    )
except ValueError as e:
    print("Validation error:", e)  # Caught the invalid event!

__post_init__ is also useful for computing derived fields based on the input data:

from dataclasses import dataclass, field
from datetime import datetime, timedelta

@dataclass
class Event:
    name: str
    start_time: datetime
    end_time: datetime
    duration: timedelta = field(init=False)  # Not set during init
    is_all_day: bool = field(init=False)
    
    def __post_init__(self):
        # Validation
        if self.end_time <= self.start_time:
            raise ValueError("End time must be after start time")
        
        # Compute derived fields
        self.duration = self.end_time - self.start_time
        
        # Check if it's an all-day event (exactly 24 hours)
        self.is_all_day = self.duration == timedelta(days=1)

event = Event(
    name="Conference",
    start_time=datetime(2025, 9, 6, 9, 0),
    end_time=datetime(2025, 9, 6, 17, 0)
)

print(f"Event '{event.name}' lasts", event.duration)
print("All-day event:", event.is_all_day)

__post_init__ provides immediate validation, clean encapsulation, and automatic execution. Use it when you need to validate relationships between multiple fields, perform data normalization, compute derived fields, or enforce business rules at object creation time.

__post_init__ ensures your objects are always in a valid state from the moment they're created, leading to more robust and maintainable code.

Questions or feedback? Feel free to reach out!