Skip to content
AITroveRead. Build. Understand.
Make this comfortable

Python dataclass InitVar: validate and derive state without storing input

Last updated: 1 Oct 20265 min read
tutorial
IntermediateBy AITrove Editorial

InitVar passes a construction-only value into __post_init__ without making it a dataclass field.

Download Python source kit

Observed contract

A frozen receipt stores its subtotal and derived total. The service percentage is accepted only during construction, validated, then omitted from fields(). Replacing the object requires a fresh percentage because it is not retained for later reconstruction.

Boundary

This example uses integer cents and an exact percentage result. Real pricing must define how fractional cents are rounded. Frozen dataclasses block ordinary reassignment but do not recursively freeze mutable objects held in fields.

Executable case

python
from dataclasses import InitVar, dataclass, field, fields, replace

@dataclass(frozen=True)
class ReceiptCharge:
    subtotal_cents: int
    service_percent: InitVar[int]
    total_cents: int = field(init=False)

    def __post_init__(self, service_percent):
        if type(self.subtotal_cents) is not int or self.subtotal_cents < 0:
            raise ValueError("invalid subtotal")
        if type(service_percent) is not int or not 0 <= service_percent <= 20:
            raise ValueError("invalid percentage")
        numerator = self.subtotal_cents * service_percent
        if numerator % 100:
            raise ValueError("fractional cent needs a rounding rule")
        object.__setattr__(self, "total_cents", self.subtotal_cents + numerator // 100)

charge = ReceiptCharge(4700, service_percent=6)
print("total", charge.total_cents)
print("stored_fields", [item.name for item in fields(charge)])
updated = replace(charge, service_percent=8)
print("replaced_total", updated.total_cents)

Output

Output
total 4982
stored_fields ['subtotal_cents', 'total_cents']
replaced_total 5076

Cost

Generated field access is constant time. __post_init__ validation runs for every construction and replacement; reject unsupported fractional-cent cases before a value enters a ledger.

Common Mistakes

  • InitVar is not available later as a stored field.
  • replace must receive a required InitVar again.
  • Frozen fields do not freeze mutable objects reachable through them.

Connected lessons

Test this contract.

python
dataclass-initvar-derived-total
Storage details