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.
Do these 3 things before closing this tab:
1Scan for outdated or missing drivers - takes under a minute2Repair Windows errors before they cause bigger problems3Fix the driver behind crashes, sound loss and screen glitchesEquality 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.
#1 Best Overall
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.
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:
Rank #2
@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.
| 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.
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.
Helper functions
The dataclasses module provides four helpers that work on any dataclass instance.
fields(obj)returns a tuple of field descriptors. It leaves outClassVarandInitVarpseudo-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.
Crashes, No Sound, or Screen Glitches?
Random freezes, missing sound and display glitches usually trace back to one bad driver. Find and replace yours safely.Free scan · under a minuteWindows Errors? Fix Them Before They Spread
Repair common Windows errors and clear accumulated junk for a smoother, more stable PC - no reinstall needed.Free scan · no reinstallIf you need a shallow dictionary rather than the recursive conversion, the documentation shows building one from fields() and getattr().
Best Value
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 thateqhas not been set toFalse. - 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=Trueoption, 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.
Quick Recap
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.

