Skip to content
v0.1.0latest release21 skills2 categories3 sub-categories22 tested scripts68 reference docsContact us

Dataclass Reference

Reference for python-dataclass-converter · 527 lines

Programming / Pythonv1.0.0Updated OCT 20 20265 references1 script

Complete reference for dataclasses (Python 3.9 baseline, newer features tagged). Examples use AI-engineering objects but every rule is general.

Contents

  1. What the decorator does
  2. Decorator options
  3. field() parameters
  4. Default values and default_factory
  5. post_init and InitVar
  6. Keyword-only fields and KW_ONLY
  7. Inheritance and field ordering
  8. Equality, ordering and hashing
  9. frozen=True
  10. slots=True and weakref_slot
  11. Module functions
  12. ClassVar, descriptors and properties
  13. Pattern matching
  14. Generics
  15. Performance and memory
  16. Error catalogue

What the decorator does

@dataclass inspects the class __annotations__ at class-creation time. Every annotated name that is not a ClassVar (and not an InitVar, which is a pseudo-field) becomes a field, in definition order. It then adds methods to the class: __init__, __repr__, __eq__, and optionally ordering methods, __hash__, __setattr__/__delattr__ (frozen), __match_args__, __slots__. It does not add runtime type checking: annotations are documentation for type checkers.

python
from dataclasses import dataclass

@dataclass
class TokenUsage:
    prompt_tokens: int
    completion_tokens: int = 0

u = TokenUsage(12, 30)
print(u)                         # TokenUsage(prompt_tokens=12, completion_tokens=30)
print(u == TokenUsage(12, 30))   # True
TokenUsage("12")                 # accepted at runtime: no validation

Rules for whether a generated method replaces one you wrote:

MethodIf you define it in the class body
__init__, __repr__, __eq__yours is kept (decorator skips it)
__lt__/__le__/__gt__/__ge__ with order=TrueTypeError
__setattr__/__delattr__ with frozen=TrueTypeError
__hash__see hash table

Introspection attributes: cls.__dataclass_fields__ (dict name -> Field), cls.__dataclass_params__, cls.__match_args__ (3.10+).

Decorator options

text
@dataclass(init=True, repr=True, eq=True, order=False, unsafe_hash=False, frozen=False,
           match_args=True, kw_only=False, slots=False, weakref_slot=False)
OptionDefaultGenerates / changesSince
initTrue__init__(self, <init fields>); calls __post_init__ if defined3.7
reprTrue__repr__ as Cls(a=1, b='x') over repr=True fields; recursion-safe3.7
eqTrue__eq__ comparing compare-fields in order; NotImplemented for a different class3.7
orderFalse__lt__, __le__, __gt__, __ge__ comparing field tuples; requires eq=True3.7
unsafe_hashFalseforce a field-based __hash__ even when mutable3.7
frozenFalseassignment/deletion raise FrozenInstanceError3.7
match_argsTrue__match_args__ = names of positional init fields3.10
kw_onlyFalseevery field keyword-only in __init__3.10
slotsFalsereturns a new class with __slots__3.10
weakref_slotFalseadd __weakref__ slot; requires slots=True3.11

The decorator can be used bare (@dataclass) or called (@dataclass(frozen=True)).

Choosing options quickly

NeedOptions
Plain mutable record@dataclass
Config / cache key / dict key@dataclass(frozen=True)
Many instances (chunks, tokens, events)@dataclass(slots=True) 3.10+
Immutable and compact@dataclass(frozen=True, slots=True)
Sortable results@dataclass(order=True) with sort key first, or field(compare=False) on the rest
Identity semantics (objects with resources, graph nodes)@dataclass(eq=False)
Long constructor, avoid positional mistakes@dataclass(kw_only=True) 3.10+

field() parameters

text
dataclasses.field(*, default=MISSING, default_factory=MISSING, init=True, repr=True,
                  hash=None, compare=True, metadata=None, kw_only=MISSING, doc=None)
ParameterMeaningTypical use
defaultdefault value (immutable!)field(default=0, repr=False) when combining with other options
default_factoryzero-arg callable invoked per instancelist, dict, lambda: ["\n"], uuid.uuid4, time.time
initinclude in __init__False for derived/internal state
reprinclude in __repr__False for secrets, embeddings, big blobs
hashinclude in __hash__; None = follow compareFalse for a field compared but not hashed
compareinclude in __eq__/orderingFalse for arrays, timestamps, caches
metadataread-only mapping, ignored by dataclassesJSON aliases, units, CLI help
kw_onlythis field keyword-only3.10+
docdocstring for the field (shown by help())3.14+

default and default_factory are mutually exclusive (ValueError). field(init=False) without a default leaves the attribute unset until __post_init__ assigns it; repr() before that raises AttributeError.

python
import time
import uuid
from dataclasses import dataclass, field, fields

@dataclass
class TraceSpan:
    name: str
    span_id: str = field(default_factory=lambda: uuid.uuid4().hex)
    started_at: float = field(default_factory=time.time, compare=False)
    attributes: dict = field(default_factory=dict)
    cost_usd: float = field(default=0.0, metadata={"unit": "USD"})

print([(f.name, f.metadata.get("unit")) for f in fields(TraceSpan)])

Default values and default_factory

Defaults are evaluated once at class creation and stored as class attributes. That is why mutable defaults are rejected:

python
from dataclasses import dataclass, field

@dataclass
class Bad:
    tags: list = []           # ValueError: mutable default <class 'list'> for field tags is not allowed: use default_factory

@dataclass
class Good:
    tags: list = field(default_factory=list)
  • Before 3.11 the check rejected only list, dict, set. Since 3.11 it rejects any default whose class is unhashable (__hash__ is None), e.g. an instance of a non-frozen dataclass with eq=True.
  • Hashable but mutable objects (a Tokenizer() instance, a requests.Session()) pass the check and are shared by all instances. Use default_factory=Tokenizer.
  • A factory with arguments: field(default_factory=lambda: defaultdict(list)) or functools.partial(...).
  • default_factory is not called if a value is passed to __init__.
  • Fields with init=False and a default_factory still get the factory value in __init__.

Immutable alternatives that work as plain defaults: (), frozenset(), "", 0, None, enum members, instances of frozen dataclasses. A read-only mapping (MappingProxyType) is unhashable, so give it a factory.

post_init and InitVar

The generated __init__ assigns fields and then calls self.__post_init__(*initvars) if the method exists.

python
from dataclasses import InitVar, dataclass, field
from typing import Optional

@dataclass
class Chunk:
    text: str
    doc_id: str
    tokenizer: InitVar[Optional[object]] = None
    n_tokens: int = field(init=False)
    normalized: str = field(init=False, repr=False)

    def __post_init__(self, tokenizer: Optional[object]) -> None:
        if not self.text:
            raise ValueError("empty chunk")
        self.normalized = " ".join(self.text.split())
        self.n_tokens = len(tokenizer.encode(self.text)) if tokenizer else len(self.text) // 4

Rules:

  • InitVar[T] fields appear in __init__ (respecting default rules) and are passed positionally to __post_init__ in declaration order. They are not stored, not in fields(), asdict(), repr, eq.
  • If a class-level default is given to an InitVar, that value is also set as a class attribute (a quirk: Chunk.tokenizer is None).
  • replace() requires every InitVar without a default to be passed explicitly (ValueError on older versions, TypeError on recent ones).
  • __post_init__ is not called when init=False is passed to the decorator.
  • Inheritance: the generated __init__ calls self.__post_init__, which resolves to the most-derived one. Call super().__post_init__(...) explicitly when a base also has one.
  • Do expensive work lazily (a property or functools.cached_property, not available with slots=True) rather than in __post_init__ when objects are created in bulk.
python
@dataclass
class BaseRecord:
    id: str
    def __post_init__(self) -> None:
        self.id = self.id.strip()

@dataclass
class EvalRecord(BaseRecord):
    score: float = 0.0
    def __post_init__(self) -> None:
        super().__post_init__()
        if not 0.0 <= self.score <= 1.0:
            raise ValueError("score must be in [0, 1]")

Keyword-only fields and KW_ONLY

Three ways (all 3.10+):

python
from dataclasses import KW_ONLY, dataclass, field

@dataclass(kw_only=True)
class A:
    model: str
    temperature: float = 0.7

@dataclass
class B:
    model: str
    _: KW_ONLY                   # everything after the sentinel is keyword-only
    temperature: float = 0.7
    seed: int                    # no default after a default: allowed because keyword-only

@dataclass
class C:
    model: str
    seed: int = field(kw_only=True, default=0)

Keyword-only fields are moved after all positional fields in the generated __init__, regardless of declaration order, and are excluded from __match_args__. On 3.9 there is no keyword-only support: order fields so defaults come last, or write __init__ by hand.

Inheritance and field ordering

Fields are gathered in MRO order (base first). A field redefined in a subclass keeps its original position but takes the new type/default.

python
from dataclasses import dataclass, field

@dataclass
class BaseRequest:
    model: str
    temperature: float = 0.7

@dataclass
class ChatRequest(BaseRequest):
    messages: list = field(default_factory=list)   # has a default: OK

@dataclass
class Broken(BaseRequest):
    messages: list                                  # TypeError: non-default argument 'messages' follows default argument

Fixes for Broken: give messages a default; make the subclass kw_only=True (3.10+); use field(kw_only=True); or prefer composition (request: BaseRequest).

Other inheritance rules:

SituationResult
frozen subclass of non-frozen dataclass (or vice versa)TypeError: cannot inherit frozen dataclass from a non-frozen one
dataclass subclass of a plain class with its own __init__base __init__ is not called by the generated one; call it from __post_init__ or convert the base
plain subclass of a dataclassinherits fields and methods; annotations in the subclass are not fields unless decorated
slots=True base and subclasseach must be decorated with slots=True for full slot coverage
multiple inheritancefields merged in reverse MRO; keep it shallow

Equality, ordering and hashing

__eq__ returns NotImplemented unless other.__class__ is self.__class__, then compares compare-fields in order as if they were tuples. Subclass instances are therefore never equal to base instances.

order=True compares (f1, f2, ...) tuples of compare-fields: put the sort key first or use sorted(key=...) instead.

python
from dataclasses import dataclass, field

@dataclass(order=True)
class RankedHit:
    neg_score: float                         # sort key first: best score first when ascending
    chunk_id: str = field(compare=True)
    text: str = field(compare=False)

hits = sorted([RankedHit(-0.8, "b", "..."), RankedHit(-0.9, "a", "...")])

Hash generation table

__hash__ is decided from unsafe_hash, eq, frozen and whether the class body defines __hash__ explicitly:

unsafe_hasheqfrozenexplicit __hash__ in classResult
FalseTrueFalseno__hash__ = None (unhashable)
FalseTrueTruenogenerated from hash-fields
FalseFalseanynoinherited (object.__hash__, identity)
Falseanyanyyesyours is kept
Trueanyanynogenerated from hash-fields
TrueanyanyyesTypeError

Hash-fields are fields with hash=True, or hash=None and compare=True. Consequences:

  • Converting a plain class to @dataclass makes instances unhashable: set() of them, lru_cache arguments and dict keys break. Use frozen=True, or eq=False to keep identity.
  • unsafe_hash=True on a mutable object: mutating a hashed field while the object sits in a set corrupts the set.
  • A frozen dataclass containing a list or np.ndarray field raises TypeError: unhashable type only when hash() is called: use tuples or hash=False.

frozen=True

python
from dataclasses import FrozenInstanceError, dataclass, replace

@dataclass(frozen=True)
class SamplingParams:
    temperature: float = 0.7
    top_p: float = 1.0

p = SamplingParams()
try:
    p.temperature = 0.0
except FrozenInstanceError as e:        # subclass of AttributeError
    print(e)                            # cannot assign to field 'temperature'
greedy = replace(p, temperature=0.0)
  • In __post_init__ (or any method) use object.__setattr__(self, "name", value).
  • Frozen is shallow: frozen.items.append(x) still works if items is a list.
  • Cost: frozen __init__ uses object.__setattr__ for every field, measurably slower for millions of objects.
  • copy.replace(obj, **changes) works on dataclasses from 3.13 (__replace__).

slots=True and weakref_slot

slots=True (3.10+) makes the decorator build a new class with __slots__ derived from the fields.

Benefits: lower memory per instance, faster attribute access, typos like obj.scroe = 1 raise AttributeError.

Caveats:

CaveatDetail
New class objectdecorators applied before @dataclass see the old class; references captured at class creation point to the old one
Zero-arg super()broken in methods of slots=True dataclasses on 3.10-3.13 (TypeError), fixed in 3.14; use super(Cls, self) when supporting older versions
functools.cached_propertyneeds __dict__; fails with slots=True
__weakref__not available unless weakref_slot=True (3.11+) - needed for weakref.ref, WeakValueDictionary caches
Manual __slots__do not combine with slots=True; a manual __slots__ plus field defaults raises ValueError: 'x' in __slots__ conflicts with class variable
Picklingsupported; frozen + slots classes get generated __getstate__/__setstate__
Adding attributes dynamicallyimpossible (no __dict__)

On 3.9, emulate with a hand-written __slots__ only when fields have no defaults, or upgrade.

Module functions

FunctionPurposeNotes
fields(obj_or_cls)tuple of Fieldexcludes ClassVar and InitVar
asdict(obj, *, dict_factory=dict)recursive dictdeep-copies non-dataclass values
astuple(obj, *, tuple_factory=tuple)recursive tuplesame deep-copy behaviour
replace(obj, /, **changes)new instance via __init__re-runs __post_init__; init=False fields cannot be passed
is_dataclass(obj)True for dataclass classes and instancesisinstance(obj, type) to tell them apart
make_dataclass(name, fields, ...)build a class dynamicallyfields as name, (name, type) or (name, type, field(...))
MISSINGsentinel for "no default"compare with is
FrozenInstanceErrorraised by frozen assignmentsubclass of AttributeError
KW_ONLYsentinel type (3.10)_: KW_ONLY
InitVarinit-only pseudo-fieldInitVar[int]

asdict deep-copy pitfalls

python
import threading
from dataclasses import asdict, dataclass, field, fields

@dataclass
class Retriever:
    index_name: str
    _lock: threading.Lock = field(default_factory=threading.Lock, repr=False, compare=False)

r = Retriever("docs")
try:
    asdict(r)
except TypeError as e:
    print(e)          # cannot pickle '_thread.lock' object

def shallow_dict(obj) -> dict:
    """Top-level fields only, no copying, skips private fields."""
    return {f.name: getattr(obj, f.name) for f in fields(obj) if not f.name.startswith("_")}
  • asdict handles namedtuple specially (rebuilds it), lists/tuples/dicts recursively, and calls copy.deepcopy on everything else.
  • A 1536-dim float32 embedding per chunk times 10 000 chunks gets copied on every asdict call.
  • dict_factory receives a list of (key, value) pairs, useful to drop Nones:
python
from dataclasses import asdict

def drop_none(pairs):
    return {k: v for k, v in pairs if v is not None}

payload = asdict(request, dict_factory=drop_none)  # applied at every nesting level

replace() semantics

python
from dataclasses import dataclass, field, replace

@dataclass(frozen=True)
class Prompt:
    template: str
    n_chars: int = field(init=False)

    def __post_init__(self) -> None:
        object.__setattr__(self, "n_chars", len(self.template))

p2 = replace(Prompt("Hi {name}"), template="Hello {name}")
print(p2.n_chars)   # 12: recomputed by __post_init__
# replace(p2, n_chars=3) -> error: field n_chars is declared with init=False, it cannot be specified with replace()

make_dataclass

python
from dataclasses import field, make_dataclass

Row = make_dataclass(
    "EvalRow",
    [("example_id", str), ("score", float), ("tags", list, field(default_factory=list))],
    frozen=False,
)
print(Row("ex1", 0.9))

Useful when the schema comes from a config (column list of an eval CSV). Prefer static classes when the shape is known, so type checkers see it.

ClassVar, descriptors and properties

python
from dataclasses import dataclass
from typing import ClassVar

@dataclass
class EmbeddingModel:
    name: str
    dim: int
    registry: ClassVar[dict] = {}           # class-level, not a field, mutable allowed
    DEFAULT_BATCH: ClassVar[int] = 64

    @property
    def is_large(self) -> bool:
        return self.dim >= 1024
  • Unannotated class attributes are not fields either (MAX = 3).
  • Properties with the same name as a field interfere with defaults; use a private field plus a property with a different name.
  • Descriptor-typed fields: the descriptor's __get__(None, cls) result is used as the default; __set__ is called by __init__.

Pattern matching

__match_args__ (3.10+) enables positional class patterns:

python
from dataclasses import dataclass

@dataclass
class TextDelta:
    text: str

@dataclass
class ToolUseDelta:
    tool_name: str
    partial_json: str

def render(event) -> str:
    match event:
        case TextDelta(text):
            return text
        case ToolUseDelta(name, _):
            return f"[calling {name}]"
        case _:
            return ""

Keyword-only fields are not in __match_args__; match them with keyword patterns: case Cfg(model="gpt-4o").

Generics

python
from dataclasses import dataclass, field
from typing import Generic, TypeVar

T = TypeVar("T")

@dataclass
class Page(Generic[T]):
    items: list = field(default_factory=list)
    next_cursor: str = ""

pages: "Page[str]" = Page(items=["a", "b"])

3.12+ syntax: class Page[T]: with items: list[T] (PEP 695). Subscripting a dataclass generic does not validate anything at runtime.

Performance and memory

Order-of-magnitude guidance (CPython 3.12, measure your own workload):

ContainerCreate 1M small objectsMemory per instanceAttribute access
plain class / @dataclassbaseline__dict__ (~100+ bytes)baseline
@dataclass(slots=True)similar or fasterroughly half or lessslightly faster
@dataclass(frozen=True)slower (object.__setattr__ per field)as abovesame
NamedTuplefastsmall (tuple)index-based, fast
dictfastlarger than slotskey lookup
pydantic v2 BaseModelseveral times slower (validation)largersame-ish
msgspec.Structfastestsmallestfast

Tips:

  • Class creation runs exec once per decorated class: negligible at import, noticeable only for make_dataclass in loops.
  • For millions of records, columnar storage (numpy arrays, Arrow, DataFrame) beats any per-object container.
  • slots=True is the cheapest memory win for chunks/tokens/events created in bulk.
  • Avoid asdict() in hot loops; write a field-by-field to_dict().

Error catalogue

ErrorCauseFix
ValueError: mutable default <class 'list'> for field x is not allowed: use default_factorylist/dict/set (3.11+: any unhashable) defaultfield(default_factory=list)
TypeError: non-default argument 'x' follows default argument (newer versions append the field name)field order (often via inheritance)reorder, defaults, kw_only
FrozenInstanceError: cannot assign to field 'x'assignment on frozen instancereplace(), or object.__setattr__ in __post_init__
TypeError: unhashable type: 'Cfg'eq=True, not frozenfrozen=True, unsafe_hash=True or eq=False
TypeError: unhashable type: 'list' from frozen hash()list field in frozen classtuple, or hash=False
ValueError: The truth value of an array with more than one element is ambiguousnumpy/tensor field in __eq__field(compare=False)
TypeError: cannot inherit frozen dataclass from a non-frozen onemixed frozen inheritancemake both frozen or neither
field x is declared with init=False, it cannot be specified with replace() (ValueError, TypeError on recent versions)replace on init=False fieldrecompute in __post_init__
InitVar 'x' must be specified with replace() (ValueError/TypeError as above)InitVar without defaultpass it or give it a default
TypeError: __init__() missing 1 required positional argumentfield without defaultdefault or pass the value
AttributeError: 'X' object has no attribute 'y' in reprinit=False field never assignedassign in __post_init__ or give a default
TypeError: cannot pickle '_thread.lock' object from asdictdeep copy of non-copyable valueshallow to_dict()
ValueError: 'x' in __slots__ conflicts with class variablemanual __slots__ with defaultsslots=True
TypeError: Cannot overwrite attribute __lt__ in class Xorder=True with hand-written orderingremove one of them
TypeError: super(type, obj): obj must be an instance or subtype of typezero-arg super() in slots=True class (3.10-3.13; fixed in 3.14)super(Cls, self)
More in Python

python-beginner-concept-tutor

Teach Python concepts to beginners heading into AI engineering (data scientists, analysts, students, career switchers): plain-language definitions, analogies, minimal examples, line-by-line walkthroughs, common mistakes, check-your-understanding questions and mini exercises; Socratic hints before solutions; explaining tracebacks; mutable vs immutable, references and aliasing, is vs ==, integer division, floating point, slicing and off-by-one, indentation errors; a staged learning path from variables to calling an LLM API, embeddings and a tiny RAG. Ships a script that lists the concepts a snippet uses in learning order and traces execution step by step like Python Tutor.

python-comprehension-refactorer

Refactor Python loops into list, dict and set comprehensions and generator expressions, and unroll comprehensions that are too clever: filtering, mapping, nested-loop flattening, conditional expressions, walrus in comprehensions, dict inversion, grouping with defaultdict/Counter, lazy generators for sum/any/all/max, streaming large files, itertools (chain, islice, groupby, batched, pairwise, accumulate, product), scoping rules, side-effect anti-patterns and performance (when map/filter or numpy/pandas vectorization win). AI-engineering focus: building prompt lists, batching texts for embeddings, filtering retrieved chunks by score, id->embedding dicts, flattening tool-call results, lazy dataset streaming and token counting.

python-context-manager-writer

Write correct Python context managers and find resource-handling bugs — __enter__/__exit__ semantics and exception suppression, contextlib (contextmanager, asynccontextmanager, ExitStack, AsyncExitStack, closing, aclosing, suppress, redirect_stdout, nullcontext, chdir), async with, re-entrancy, leaked files/connections/clients/pools/locks, and AI-engineering recipes: torch.no_grad/inference_mode/autocast, eval()/train() switches, GPU cache cleanup, token and latency meters, tracing spans, temp env vars, rate-limit semaphores, httpx/aiohttp client lifecycle, FastAPI lifespan, DB/vector-store transactions, atomic checkpoint writes, seeds and agent sandboxes.

python-exception-handling-designer

Design and review Python exception handling: custom exception hierarchies with retryable flags, what to catch where, EAFP vs LBYL, chaining with raise ... from, else/finally, logger.exception, add_note, ExceptionGroup and except* with asyncio.TaskGroup, warnings, asserts vs validation, FastAPI error translation, retries with backoff and jitter, timeouts, cancellation, pytest.raises, plus LLM-app failures: 429 rate limits with retry-after, 5xx overloaded, context-length exceeded, content filters, broken streams, malformed JSON repair loops, tool-call errors fed back to the model, partial embedding batch failures, CUDA OOM batch halving, vector DB outages, circuit breakers and model fallbacks.

python-idiomatic-code-reviewer

Review Python code for non-idiomatic, unpythonic or risky patterns and rewrite it into clean modern Python: PEP 8 / PEP 20, EAFP vs LBYL, iteration idioms (enumerate, zip, comprehensions, any/all), unpacking, f-strings, pathlib, truthiness, `is None`, mutable defaults, walrus, match statements, collections / itertools / functools, logging instead of print, with-blocks and resource handling. Includes AI-engineering checks: hardcoded API keys, time.sleep or requests inside async def, missing HTTP timeouts, unbounded retry loops, model clients built at import time, prompt string concatenation, sequential awaits, eval() on LLM output. Ships a 46-rule AST reviewer with severities and suggested rewrites.

python-type-hints-annotator

Add accurate, modern type hints to Python and fix mypy / pyright (pyrefly, ty) errors: builtin generics, X | None, TypeAlias and the 3.12 type statement, TypeVar and PEP 695 generics, Protocol, Callable / ParamSpec / Concatenate, TypedDict (Required, NotRequired, ReadOnly), Literal, Final, ClassVar, overload, Annotated, TypeGuard / TypeIs, Self, Never, TypeVarTuple, async types, Iterable vs Sequence vs Mapping, TYPE_CHECKING, stubs, py.typed, gradual strictness. AI-engineering focus: typed LLM message dicts and tool schemas, pydantic vs TypedDict, numpy NDArray, torch / jaxtyping shapes, streaming generators, async clients, callbacks, generic VectorStore protocols, typed config. Ships an AST audit: coverage gate, legacy-typing rewrites, inferred types.