Skip to content
Foxipa

Suggest an edit

Tell us what should be corrected, clarified, or improved. Submission will be connected to Foxipa’s contribution service later.

No information is sent anywhere yet. Your suggestion is only prepared in your browser.

Learning

Python Dataclasses

Use dataclasses to model records with annotated fields, generated methods, safe mutable defaults, post-initialization checks, and controlled immutability.

Levels: Intermediate ~18 min read

Intermediate

A dataclass is a convenient way to define a class whose main job is to store named data. The @dataclass decorator reads the class's annotated fields and generates selected methods for you. It remains a normal Python class, so you can add your own methods and use it with ordinary inheritance.

What a dataclass gives you

By default, @dataclass generates an initializer (__init__), a developer-friendly representation (__repr__), and value-based equality (__eq__). Fields are taken from annotated class variables, in definition order. The decorator does not create a new class.

Interactive Runnable example
Edit

Ready to run.

Add ordinary methods when they make the data easier to use. For a dataclass, prefer to write the behavior that is specific to your class and let the decorator provide routine field handling.

Quick check

Which behavior is generated by default for this dataclass?

from dataclasses import dataclass

@dataclass
class Label:
    text: str

label = Label("draft")
print(label)

Fields, defaults, and type annotations

A field is identified by an annotation in the class body. Those annotations describe the intended data; @dataclass does not generally enforce the annotated type at runtime. Fields without default values must come before fields with defaults in the generated initializer, including when inherited fields are involved.

Interactive Runnable example
Edit

Ready to run.

Quick check

Do dataclass field annotations automatically reject values of the wrong type?

from dataclasses import dataclass

@dataclass
class Count:
    value: int

item = Count("many")
print(item.value)

Give each instance its own mutable default

Do not use one mutable list or dictionary as a field default for every instance. Use field(default_factory=...) so the factory is called separately when an instance needs a value. The factory may be list, dict, set, or another zero-argument callable.

Interactive Runnable example
Edit

Ready to run.

Quick check

How should a dataclass field receive a fresh list for each instance?

from dataclasses import dataclass, field

@dataclass
class Bucket:
    items: list[str] = field(default_factory=list)

left = Bucket()
right = Bucket()
left.items.append("pen")
print(right.items)

Use __post_init__ for checks and derived fields

When the decorator generates __init__, it calls __post_init__ after the fields have been assigned. Use this hook for a small invariant check or a value derived from other fields. If you write your own __init__, do not assume the generated initializer will call __post_init__ for you.

Interactive Runnable example
Edit

Ready to run.

Quick check

When does the generated initializer call __post_init__?

from dataclasses import dataclass, field

@dataclass
class Box:
    width: int
    height: int
    area: int = field(init=False)

    def __post_init__(self):
        self.area = self.width * self.height

print(Box(3, 4).area)

Equality and frozen instances

Generated equality compares fields when the other object has the same concrete class. By default, dataclasses do not generate ordering comparisons. frozen=True blocks ordinary attribute assignment after construction, but it is not deep immutability: a mutable object stored in a field can still be changed through that field.

Interactive Runnable example
Edit

Ready to run.

Quick check

What does frozen=True guarantee?

from dataclasses import dataclass, field

@dataclass(frozen=True)
class Tags:
    values: list[str] = field(default_factory=list)

tags = Tags()
tags.values.append("core")
print(tags.values)

Choose a dataclass when it fits

A dataclass is a good fit for a record-like object with named fields and predictable generated behavior. Use an ordinary class when construction needs a substantially different lifecycle, or when its main purpose is behavior rather than stored data. Keep validation explicit and choose frozen or ordering options only when their semantics match the design.

Was this page helpful?

Mark this learning resource complete to track your learning progress.