Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

iTechGuides is reader-supported. When you buy through links on our site, we may earn an affiliate commission. As an Amazon Associate I earn from qualifying purchases. Learn more

The @dataclass decorator, from Python’s standard dataclasses module, turns a class with annotated attributes into a data-holding class. By default it generates an __init__, a readable __repr__, and an __eq__ method, so you can write a few field declarations instead of boilerplate. The sections below explain what it generates, how to declare fields and defaults, and when options such as frozen, order, kw_only, and slots are worth using. The reference for this article is the Python 3.13 dataclasses documentation; version-specific behavior is flagged where it matters.

What @dataclass generates

Import the decorator from dataclasses and place it directly above the class. Each annotated class variable becomes a field, and the decorator uses those fields to build special methods. It returns the same class you wrote; it does not substitute a new one.

from dataclasses import dataclass

@dataclass
class Point:
    x: float
    y: float

p = Point(2.0, 3.5)
print(p)                          # Point(x=2.0, y=3.5)
print(p == Point(2.0, 3.5))       # True

The annotations are used to find fields and to decide some generated behavior. They are not general runtime type checks: Point(2.0, "three") is accepted without complaint. The main exceptions in the standard library are the ClassVar and InitVar annotations, which change how a name is treated. If you need values validated, add a check in __post_init__() or use a validation library.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Equality deserves a note. The generated __eq__ compares fields and requires both objects to be of the identical type. In Python 3.13 the comparison checks fields individually; Python 3.12 and earlier compared tuples of fields. The two approaches can differ in edge cases involving values such as NaN, so if you compare objects containing floats across versions, test that code on the interpreter you deploy.

Declaring fields and defaults

A simple immutable default can be written as a normal class attribute value:

from dataclasses import dataclass

@dataclass
class Server:
    host: str
    port: int = 8080
    debug: bool = False

Do not use this pattern for mutable containers. A list, dict, or set shared across every instance is a bug waiting to happen. For per-instance containers, use field(default_factory=...), which calls the factory each time an instance is created:

from dataclasses import dataclass, field

@dataclass
class Playlist:
    name: str
    tracks: list[str] = field(default_factory=list)

a = Playlist("Morning")
b = Playlist("Evening")
a.tracks.append("Song 1")
print(b.tracks)   # []

The field() function also controls how a field takes part in generated methods. Its main arguments are:

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
  • init: whether the field is a parameter of the generated __init__.
  • repr: whether the field appears in the generated __repr__.
  • compare: whether the field participates in equality and ordering comparisons.
  • hash: whether the field is included in the generated hash.
  • metadata: a mapping for third-party tools that read field information.
  • kw_only: whether the field must be passed by keyword.

Field order matters. A field without a default cannot follow a field with a default, and this also applies when a subclass adds fields to a parent:

@dataclass
class Broken:
    x: int = 0
    y: int        # TypeError: non-default argument 'y' follows default argument

Forcing keyword-only arguments

To require callers to pass a field by name, set kw_only=True on that field, or place a KW_ONLY sentinel before the fields that should be keyword-only:

from dataclasses import dataclass, field, KW_ONLY

@dataclass
class Config:
    host: str
    port: int = field(default=8080, kw_only=True)

@dataclass
class Config2:
    host: str
    _: KW_ONLY
    port: int = 8080
    debug: bool = False

Config("localhost", port=9000)   # port must be a keyword
Config2("localhost", debug=True)

Keyword-only fields are left out of __match_args__, so they are not available as positional patterns in match statements.

Decorator options at a glance

The table lists the principal arguments to @dataclass and their defaults, as documented in the Python 3.13 reference.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Option Default Effect Notes
init True Generates __init__ unless the class already defines one. Use init=False only when you write your own initializer.
repr True Generates __repr__ unless the class already defines one. Field-level repr=False hides a sensitive or bulky field.
eq True Generates field-based equality. Requires identical instance types; see the Python 3.13 note above.
order False Generates <, <=, >, and >=. Requires eq=True.
frozen False Raises FrozenInstanceError on attribute assignment or deletion. Emulates read-only instances; it is not true immutability.
unsafe_hash False Forces generation of __hash__. Otherwise hashing follows the combination of eq and frozen.
match_args True Generates __match_args__ from positional, non-keyword-only fields. Controls positional pattern matching.
kw_only False Makes all fields keyword-only by default. Added in Python 3.10.
slots False Generates __slots__ for the class. Added in Python 3.10.
weakref_slot False Adds a weak-reference slot when slots are used. Added in Python 3.11; requires slots=True.

Choosing frozen, order, and slots

frozen=True for read-only values

Frozen instances are useful for values that should not change after creation, such as coordinates or money amounts that are passed around a program:

from dataclasses import dataclass

@dataclass(frozen=True)
class Money:
    amount: int
    currency: str

m = Money(10, "USD")
m.amount = 20   # dataclasses.FrozenInstanceError

Treat frozen=True as a guard against accidental reassignment, not as a guarantee. The generated initializer has to set attributes through object.__setattr__, which carries a small performance cost. Code can still bypass the check with that same mechanism, and a mutable field such as a list inside a frozen instance can still be changed in place.

order=True for sorting

Ordering is off by default. Turn it on when instances have a natural sequence, and remember that comparisons use the fields in declaration order:

from dataclasses import dataclass

@dataclass(order=True)
class Version:
    major: int
    minor: int

print(sorted([Version(1, 3), Version(1, 2), Version(0, 9)]))
# [Version(major=0, minor=9), Version(major=1, minor=2), Version(major=1, minor=3)]

Ordering requires equality; setting order=True while eq=False is an error.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

slots=True for lighter instances

Slotted classes store attributes in a fixed layout instead of an instance __dict__. Use slots=True when you create many small instances and do not depend on setting arbitrary new attributes at runtime:

from dataclasses import dataclass

@dataclass(slots=True)
class Reading:
    sensor: str
    value: float

r = Reading("temp", 21.5)
r.note = "x"   # AttributeError: 'Reading' object has no attribute 'note'

Slots require Python 3.10 or later. Add weakref_slot=True only if you need weak references to these instances; it requires Python 3.11 or later and slots=True. Slotted classes also interact differently with some inheritance and pickling patterns, so check that code path before adopting the option across a codebase.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Helper functions

The dataclasses module provides four helpers that work on any dataclass instance.

  • fields(obj) returns a tuple of field descriptors. It leaves out ClassVar and InitVar pseudo-fields, so it is the safe way to list the data a class stores.
  • asdict(obj) converts an instance to a dictionary, recursing into nested dataclasses, lists, tuples, and dictionaries. Other values are deep-copied.
  • astuple(obj) does the same conversion to a tuple.
  • replace(obj, **changes) returns a new instance with selected fields changed.
from dataclasses import dataclass, asdict, replace

@dataclass
class Point:
    x: float
    y: float

p = Point(2.0, 3.5)
print(asdict(p))            # {'x': 2.0, 'y': 3.5}
print(replace(p, y=10.0))   # Point(x=2.0, y=10.0)

Two details matter when you use replace(). It calls the class initializer, so __post_init__() runs again on the new object. Fields declared with init=False cannot be supplied as changes, because they are not initializer parameters.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

If you need a shallow dictionary rather than the recursive conversion, the documentation shows building one from fields() and getattr().

Common errors and fixes

  • TypeError: non-default argument follows default argument. Reorder the fields so every field without a default comes first, or give the later field a default, or make it keyword-only.
  • FrozenInstanceError on assignment. The class is frozen. Create a new instance with replace() instead of mutating the existing one.
  • Ordering comparison fails or is missing. Confirm that the decorator includes order=True, and that eq has not been set to False.
  • Equality results differ between environments. Compare the Python version. Python 3.13 changed how generated equality compares fields, which matters for edge cases such as NaN.
  • Unexpected extra attributes fail with slots. Remove the slots=True option, or store the data in a declared field.

Version notes for tutorials and production code

The behavior described here comes from the Python 3.13 library reference. Options added after Python 3.9 are not available in older interpreters: kw_only and slots arrived in Python 3.10, and weakref_slot in Python 3.11. Equality changed in Python 3.13. If your project supports several Python versions, state the minimum version in your docs and avoid these options when that minimum is older than the option.

Newer Python releases may refine the module further. When in doubt, check the dataclasses reference for the exact interpreter version you use.

The Bottom Line

Use plain @dataclass for ordinary data holders, field(default_factory=...) for mutable defaults, and add frozen, order, kw_only, or slots only when the behavior you want is clear and your minimum Python version supports it. Remember that frozen=True discourages changes rather than preventing them.

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.