__post_init__
__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!