Recommended Free Tools
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
Python’s dataclasses module turns an annotated class into a concise record-like model. The @dataclass decorator can generate an initializer, representation, equality, and other methods from the fields you declare, while leaving your own methods under the rules for each option. It is part of the standard library (added in Python 3.7), so no third-party package is required.
Dataclasses describe structure; they are not runtime type validators. An annotation such as quantity: int identifies a field for dataclass processing, but the generated code does not generally reject a string assigned to that field. Use a separate validation step when values come from untrusted or loosely typed input.
How do I use dataclasses for structured application data in Python?
Import dataclass, annotate the attributes that make up one record, and apply the decorator:
Free tools Windows power users keep installed
One-click scans. No signup required.
from dataclasses import dataclass
@dataclass
class InventoryItem:
name: str
unit_price: float
quantity_on_hand: int = 0
def total_cost(self) -> float:
return self.unit_price * self.quantity_on_hand
item = InventoryItem("Notebook", 4.50, 12)
print(item) # InventoryItem(name='Notebook', unit_price=4.5, quantity_on_hand=12)
print(item.total_cost()) # 54.0
The decorator examines annotated class variables and, unless excluded as special cases, treats them as fields in class-definition order. It can generate __init__, __repr__, and __eq__; ordering methods such as __lt__ are opt-in. A method you write yourself is not blindly overwritten: the result depends on the specific decorator parameter and whether the class already defines that method. The official behavior is documented at Python’s dataclasses documentation.
#1 Best Overall
What the generated initializer means
For the example, the generated signature is effectively InventoryItem(name, unit_price, quantity_on_hand=0). Fields without defaults must come before fields with defaults, including fields inherited from a base dataclass. Violating that rule raises TypeError while the class is created, rather than when an instance is constructed.
Annotations identify fields, but do not validate values
This succeeds unless your own code rejects it:
bad = InventoryItem("Notebook", "free", 12) # no automatic int/float validation
Static type checkers can flag the mismatch, and a validation layer can enforce constraints at an input boundary. Keep those responsibilities separate from dataclass generation; the decorator itself generally does not inspect the annotated type. ClassVar annotations and InitVar parameters are notable exceptions to ordinary stored fields.
Field defaults: constants versus per-instance values
Use a normal default for an immutable-like constant
A scalar default is appropriate when every instance can start with the same value:
Do these 3 things before closing this tab:
1Clear out junk files and repair common Windows errors2Fix the driver behind crashes, sound loss and screen glitches3Repair Windows errors before they cause bigger problemsfrom dataclasses import dataclass
@dataclass
class RetryPolicy:
attempts: int = 3
backoff_seconds: float = 1.0
Give mutable fields a fresh value with default_factory
Never place one shared list, dictionary, or set directly in a field default. Use a zero-argument factory so each instance receives a new object:
from dataclasses import dataclass, field
@dataclass
class UserProfile:
username: str
roles: list[str] = field(default_factory=list)
preferences: dict[str, str] = field(default_factory=dict)
first = UserProfile("ada")
second = UserProfile("grace")
first.roles.append("admin")
assert second.roles == []
The factory is called only when a default is needed and must accept no arguments. In Python 3.11, dataclasses reject unhashable defaults as a practical approximation for detecting mutable values; earlier documentation described checks specifically for list, dict, and set. This safeguard is not a substitute for thinking about ownership of nested objects.
Customize a field’s participation
field() can independently control whether a value appears in the generated initializer (init), representation (repr), equality and ordering (compare), hashing (hash), or keyword-only parameters (kw_only). Its metadata mapping is reserved for third-party extensions; dataclasses itself does not consume it.
Rank #2
from dataclasses import dataclass, field
@dataclass
class ApiToken:
token: str = field(repr=False)
created_by: str = field(compare=False)
scopes: tuple[str, ...] = ()
Here the token is omitted from the representation and created_by does not affect generated comparisons. Make these exclusions only when they express a real security, API, or comparison requirement.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
Equality, ordering, hashing, and frozen instances
Equality is type-sensitive and field-based
With defaults, two dataclass instances compare as tuples of their fields, in declaration order, and they must have the identical class type:
@dataclass
class Point:
x: int
y: int
Point(1, 2) == Point(1, 2) # True
A different class containing the same values is not considered equal. Fields marked compare=False are left out of generated equality (and ordering) comparisons.
Ordering must be requested deliberately
Set order=True to generate __lt__, __le__, __gt__, and __ge__ using field order. Ordering is disabled by default. Requesting ordering while disabling equality (eq=False) raises ValueError, because the generated ordering relies on equality semantics.
@dataclass(order=True)
class Priority:
level: int
label: str
Use ordering only when lexicographic field order represents a meaningful business order; otherwise write an explicit key or comparison method.
Frozen means assignment-style read-only behavior
frozen=True adds generated attribute methods that raise FrozenInstanceError when code assigns to or deletes a field:
from dataclasses import dataclass
@dataclass(frozen=True)
class Coordinate:
latitude: float
longitude: float
point = Coordinate(51.5, -0.1)
# point.latitude = 52.0 # raises FrozenInstanceError
Python’s documentation states: “It is not possible to create truly immutable Python objects.” Frozen dataclasses emulate immutability for normal attribute assignment; a nested list or dictionary can still be mutable, and other escape hatches in Python remain possible.
Hashing follows eq and frozen
When both equality and freezing are enabled, a hash is generated by default. A class with equality enabled but not frozen is normally unhashable, because mutation could invalidate its hash. unsafe_hash=True forces a hash in specialized cases and should be used cautiously: hashability does not make mutable state safe to change while an object is in a set or dictionary key.
Constructor and memory-layout options
The decorator’s options solve different problems. Their availability also depends on the Python version:
| Option | Added | Effect | Important constraint |
|---|---|---|---|
kw_only=True |
Python 3.10 | All generated initializer parameters are keyword-only. | Callers must use names such as Item(name="x"). |
slots=True |
Python 3.10 | Generates __slots__ and returns a new class. |
Changes instance layout and can affect inheritance and code expecting __dict__. |
weakref_slot=True |
Python 3.11 | Adds a __weakref__ slot. |
Requires slots=True. |
frozen=True |
Python 3.7 | Blocks ordinary assignment and deletion. | It is not deep immutability. |
default_factory=... |
Python 3.7 | Creates a default value per instance. | The factory must take no arguments. |
Keyword-only APIs
Keyword-only fields make call sites self-documenting and let you add optional parameters without relying on positional order:
from dataclasses import dataclass
@dataclass(kw_only=True)
class Connection:
host: str
port: int = 443
timeout: float = 5.0
connection = Connection(host="api.example", timeout=10.0)
The class-level option was added in Python 3.10. Individual fields can also use field(kw_only=True) when only selected parameters should require names.
Slots and weak references
slots=True creates a slotted dataclass and returns a new class rather than simply mutating the original class object. This can impose useful layout constraints, but do not promise a universal speed or memory gain: results depend on Python version, inheritance, and workload, so benchmark your application if that trade-off matters. In Python 3.11, inherited slot names are not duplicated. Because a slot name is not a reliable field inventory, use fields() instead of inspecting __slots__. Add weakref_slot=True only when weak references are required; it is available from Python 3.11 and requires slots.
Lifecycle hooks and inheritance
Validate or derive values in __post_init__
The generated initializer calls __post_init__() if the class defines it. This is a convenient place to derive attributes or perform explicit checks:
from dataclasses import dataclass, field
@dataclass
class OrderLine:
unit_price: float
quantity: int
total: float = field(init=False)
def __post_init__(self) -> None:
if self.unit_price < 0 or self.quantity < 0:
raise ValueError("price and quantity must be non-negative")
self.total = self.unit_price * self.quantity
A generated initializer does not automatically call a non-dataclass base class’s __init__. If that base initialization is needed, call it from __post_init__ (or provide another explicit construction path).
Use InitVar for construction-only input
An InitVar appears as an initializer parameter and is passed to __post_init__, but it is not stored as a normal field. This is useful for a password, configuration object, or other temporary construction input. It is excluded from fields() and ordinary serialization.
Inspecting fields and converting dataclasses
Enumerate real fields with fields()
fields() accepts a dataclass class or instance and returns Field objects. It excludes pseudo-fields such as ClassVar and InitVar:
from dataclasses import fields
for definition in fields(OrderLine):
print(definition.name, definition.type, definition.init)
This is the supported way to discover dataclass fields for generic UI, logging, or mapping code. It is more robust than reading __annotations__ or slots because it reflects dataclass-specific exclusions and options.
PC Slower Than It Used to Be?
A free scan shows the junk files, broken settings and background clutter dragging Windows down - then fixes them in one click.Free scan · Windows 10 & 11Outdated Drivers Are Slowing You Down
One free scan finds every outdated or missing driver and matches the right update for your exact hardware.Free scan · exact hardware matchHow do I convert a dataclass to a dict?
Call asdict() when you want a recursively converted data structure:
Best Value
from dataclasses import asdict
payload = asdict(order_line)
asdict() recursively converts nested dataclass instances, dictionaries, lists, and tuples. For other objects it applies copy.deepcopy(). Consequently, it is not a shallow view and can copy large or identity-sensitive values.
For a shallow mapping that reads the current field values without recursive conversion, iterate over fields() yourself:
from dataclasses import fields
def shallow_dict(instance):
return {definition.name: getattr(instance, definition.name)
for definition in fields(instance)}
Choose the shallow form when preserving object identity matters or when deep copying would be unnecessarily expensive. Neither form automatically turns arbitrary values into JSON; a JSON encoder still needs values it understands.
Quick wins for a faster PC:
Repair Windows errors before they cause bigger problemsFix Now →Scan for outdated or missing drivers - takes under a minuteDriver Scan →Clear out junk files and repair common Windows errorsFree Scan →Copy with changes using replace()
replace(instance, **changes) constructs a new instance through the class initializer, so __post_init__ runs again:
from dataclasses import replace
updated = replace(order_line, quantity=3)
Fields declared with init=False are not copied by replace(). They must be recomputed by __post_init__ or handled through a deliberate custom method. Be especially careful when such fields hold caches, handles, or other state that cannot be reconstructed from initializer arguments.
A decision checklist for production models
- Need generated construction and representation? Start with plain
@dataclassand annotations. - Need a mutable collection per object? Use
field(default_factory=...), never a shared mutable literal. - Need input checks? Implement explicit validation or use a dedicated validation layer; annotations alone do not enforce types.
- Should callers name every argument? Use
kw_only=True(Python 3.10 or newer). - Should instances reject normal reassignment? Consider
frozen=True, while documenting that nested values are not deeply immutable. - Does field order define a legitimate sort order? Opt into
order=True; otherwise keep ordering off. - Will objects be dictionary keys? Review
eq,frozen, and mutation together before relying on generated hashing. - Do you need slots or weak references? Confirm Python 3.10/3.11 availability and test inheritance, introspection, and framework compatibility.
- Need serialization? Decide between recursive
asdict(), a shallowfields()mapping, and an explicit format for non-JSON values.
For the complete parameter rules, generated-method behavior, and version notes, consult the Python 3.11 dataclasses reference and the design rationale in PEP 557.
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.

