post_init 是 dataclass 初始化后自动调用的校验与衍生计算方法,用于字段验证、联合校验及 init=False 字段赋值,须只读校验、避免覆盖用户输入,错误时抛 ValueError 并附清晰信息。

在 @dataclass 中,__post_init__ 是初始化后自动调用的方法,适合做字段的二次验证或衍生字段计算。它不能修改 __init__ 的参数签名,但可以访问所有已赋值的字段(包括默认值、field(default_factory=...) 初始化后的结果)。
确保字段已正确初始化后再验证
__post_init__ 执行时,所有字段已完成赋值(含默认值和工厂函数返回值),因此可安全读取并校验。注意:不要在 __post_init__ 里给未声明为 init=False 的字段重新赋值——这会覆盖用户传入的值,且可能破坏预期行为。
- ✅ 正确:检查
self.age > 0、len(self.name) > 2 - ❌ 错误:写
self.name = self.name.strip()(除非你明确想归一化且接受副作用) - ⚠️ 注意:若字段用了
field(init=False),它不会出现在__init__参数中,必须在__post_init__中手动赋值
抛出清晰的 ValidationError
验证失败时建议抛出 ValueError(标准做法),或自定义异常类以便上层捕获区分。消息中应包含字段名和具体原因,便于调试。
例如:
def __post_init__(self):
if self.price < 0:
raise ValueError(f"price must be non-negative, got {self.price}")
if not self.title or len(self.title.strip()) == 0:
raise ValueError("title cannot be empty or whitespace-only")
处理依赖多个字段的联合验证
__post_init__ 是唯一能同时访问所有字段的地方,适合做跨字段逻辑检查,比如“结束时间不能早于开始时间”、“密码和确认密码必须一致”等。
- ✅ 可以访问
self.start_time和self.end_time并比较 - ✅ 可以基于
self.email和self.is_verified做业务规则判断 - ? 提示:把复杂校验逻辑抽成私有方法(如
self._validate_date_range()),提升可读性
避免意外覆盖或重复计算
如果字段是通过 default_factory 构造的(如 list、dict),注意它是可变对象,直接修改(如 self.items.append(...))会影响实例状态,但不属于“验证”,而是副作用。验证应只读不改。
若需派生只读字段(如 full_name),推荐用 property;若必须缓存计算结果,可用 field(init=False) + 在 __post_init__ 中赋值:
@dataclass
class Person:
first: str
last: str
full_name: str = field(init=False)
def __post_init__(self):
if not self.first or not self.last:
raise ValueError("first and last names are required")
self.full_name = f"{self.first} {self.last}"









