dataclass defaults, default_factory and slots=True
Mutable default arguments are the oldest trap in Python, and dataclasses handle them better than functions do. Knowing exactly how helps you spot the same bug where no error is raised.
What the dataclass does
@dataclass
class Order:
tags: list = []
This raises ValueError: mutable default <class 'list'> for field tags is not allowed: use default_factory when the class is defined. The check covers list, dict and set defaults. The reason is that a default value is created once, when the class is defined, and shared by every instance, so a.tags.append('x') would appear in b.tags as well.
The correct form creates a new list per instance:
@dataclass
class Order:
tags: list = field(default_factory=list)
Where the same bug passes silently
The dataclass check only looks for the built-in unhashable types. A default instance of your own mutable class is accepted, and so are defaults in ordinary functions:
def add_tag(tag, tags=[]): # one list shared by every call
tags.append(tag)
return tags
The usual pattern is tags=None followed by if tags is None: tags = []. Linters (ruff's B006, for example) flag mutable defaults in function signatures, so it is worth having that rule on.

slots=True
@dataclass(slots=True), available since Python 3.10, generates __slots__ from the fields. We checked the effect: hasattr(instance, '__dict__') is False, so instances are smaller, but you cannot assign attributes that are not fields.
Some behaviours change with it:
- The decorator builds a new class, so code that captured a reference to the original class object, or that uses zero-argument
super()inside methods, can behave unexpectedly on some Python versions. Test it if you rely on either. - Fields with defaults are fine, but you cannot use a class attribute with the same name as a slot for a default outside the dataclass mechanism.
__weakref__is not included by default. Useweakref_slot=True(Python 3.11 and later) if you need weak references.frozen=Truecombines well with slots for immutable value objects.
A related trap: shared state through the field type
default_factory protects the field's own default. It does not protect against passing a mutable object into the constructor and keeping using it elsewhere. If an Order is built from a list owned by the caller, the two share it. Copy defensively at the boundary (list(tags)) when the class must own its data.
Equality, ordering and hashability come from the same fields
By default a @dataclass generates __eq__ comparing all fields, and __hash__ follows a rule tied to mutability: a plain mutable dataclass gets __hash__ set to None (making instances unhashable, so they cannot go in a set or be a dict key), while frozen=True makes it hashable based on the same fields used for equality. order=True additionally generates __lt__, __le__, __gt__ and __ge__, comparing fields in the order they are declared, as if the instance were a tuple of its fields; there is no way to give one field more weight than its position implies without writing the comparison by hand.
For updating an otherwise immutable (frozen=True) instance, dataclasses.replace(order, tags=[...]) returns a new instance with the given fields changed and the rest copied, which is the supported way to get an "updated copy" without making the class mutable. dataclasses.fields(Order) lists the field definitions themselves (name, type, default), which is useful for writing generic code, such as a serialiser, that should work across any dataclass without hardcoding field names.