Dataclass fields and generated behavior

default_factory, frozen, order, ClassVar, InitVar, and __post_init__

The `@dataclass` decorator generates `__init__`, `__repr__`, `__eq__`, and optionally ordering methods and `__hash__`. Field options control the generated behavior. `default_factory` solves the shared-mutable-default problem by calling a factory for each instance. `frozen=True` generates setter methods that raise `FrozenInstanceError`. `order=True` generates all comparison operators. `slots=True` (Python 3.10+) stores fields in `__slots__` descriptors, reducing memory per instance by eliminating `__dict__`. `InitVar` fields are passed to `__init__` but not stored, useful for computed initialization. `ClassVar` fields are excluded from all generated methods. Field ordering requires that fields without defaults come before fields with defaults. <a href="/classes-data-builders">Compare dataclass with NamedTuple and namedtuple</a>. <a href="/language-mutable-defaults">Understand the mutable default problem that default_factory solves</a>.

Understand.
Visualize.
Master.

Python in Depth

An interactive engineering reference for Python internals

Quick note

Treat dataclass flags as public behaviour choices.

:)
Python version

Targets Python 3.10–3.14. Python 3.9 and below are End-of-Life.

TABLE OF CONTENTS
5.2Dataclass fields and generated behavior

default_factory, frozen, order, ClassVar, InitVar, and __post_init__

Dataclass field policy decides much more than constructor spelling. Defaults, factories, comparison, repr, slots, keyword-only layout, and descriptor behavior all flow through it.

Core answer

Use field options to make generated behavior explicit. Prefer default_factory for mutable values, slots=True when open attribute dictionaries are not part of the contract, and frozen value objects when hashability follows immutability.

# [CURRENT - 3.10-3.14] Works on Python 3.10+ [PEP 557]
from dataclasses import dataclass, field
@dataclass(slots=True, kw_only=True)
class RetryPlan:
order_id: str
max_attempts: int
errors: list[str] = field(default_factory=list, repr=False)
def record_failure(plan: RetryPlan, reason: str) -> RetryPlan:
plan.errors.append(reason)
return plan
plan = RetryPlan(order_id="ORD-7", max_attempts=3)
print(record_failure(plan, "timeout"))
print(plan.errors)

Why this design exists

PEP 557 lets records keep normal Python class behavior while generated methods remove boilerplate. Field-level configuration exists because record semantics are not one-size-fits-all: constructor parameters, repr exposure, equality cost, and hash safety may differ per field.

Mechanics and CPython internals

The decorator examines annotated fields during class creation and generates methods. ClassVar and InitVar change field treatment. slots=True asks dataclasses to return a slotted class layout. Descriptor-typed defaults participate in descriptor lookup instead of becoming ordinary literal defaults.

# [CURRENT - 3.10-3.14] Works on Python 3.10+
from dataclasses import dataclass
class PositiveInt:
def __set_name__(self, owner: type[object], name: str) -> None:
self.name = "_" + name
def __get__(self, obj: object | None, owner: type[object] | None = None) -> object:
if obj is None:
return self
return getattr(obj, self.name)
def __set__(self, obj: object, value: int) -> None:
if value < 1:
raise ValueError("value must be positive")
setattr(obj, self.name, value)
@dataclass
class Window:
width: int = PositiveInt()
window = Window(5)
print(window.width)

Complexity and tradeoffs

Generated equality compares the fields it includes, so large mutable payload fields can make comparison expensive or semantically wrong. Slots can reduce per-instance overhead and block arbitrary new attributes, but that narrower layout affects weak references and some metaprogramming patterns. Hash generation follows safety policy rather than convenience.

Idiomatic patterns and refactoring

Refactor mutable literal defaults into factories and key objects into frozen records.

# [CURRENT - 3.10-3.14] Works on Python 3.10+
from dataclasses import dataclass, field
@dataclass
class Draft:
labels: list[str] = field(default_factory=list)
@dataclass(frozen=True, slots=True)
class CacheKey:
tenant: str
region: str
def attach(draft: Draft, label: str) -> Draft:
draft.labels.append(label)
return draft
print(attach(Draft(), "retry").labels)
print({CacheKey("TEN-1", "BR"): "warm"})

Common mistakes and edge cases

Do not force unsafe_hash=True on mutable equality state because a dict needs a key today. Do not expose secrets in generated repr fields. Do not read __slots__ as a dataclass field registry; use dataclasses.fields.

When to use / When NOT to use

Use dataclass fields when generated method policy should be declarative and visible next to the field definitions.

Do not stretch dataclasses into full validation frameworks when an explicit constructor, descriptor, property, or separate parser makes the lifecycle clearer.

Further reading

  • Official docs: dataclass parameters
  • Official docs: descriptor-typed fields
  • PEP 557: data classes
  • PEP 526: variable annotations
  • CPython source: dataclasses
BOARD NOTESContext
WHY NO BENCHMARK?

This topic is better taught with structure, semantics, and cross-references than with a synthetic chart.

Treat dataclass flags as public behaviour choices.

RELATED GUIDES
NEXT CHECKS
Contribute