šŸš€ LEVEL UP TO SENIOR:Unlock 500+ Advanced Practical Challenges & Exercises.
šŸŽ“ COURSERA PARTNER:Earn professional Google, Meta, and IBM certificates to supercharge your resume.
HTML MASTER CLASS /// LEARN TAGS /// BUILD STRUCTURE /// SEMANTIC WEB /// HTML MASTER CLASS /// LEARN TAGS ///

Python Dataclasses

Stop hand-writing __init__, __repr__, and __eq__ for every data-holding class — @dataclass generates them for you, correctly, and integrates with type hints and pattern matching.

⚔ Total XP: 0|šŸ’» python XP: 0

Skill Matrix

UNLOCK NODES BY LEARNING NEW TAGS.

System Hub

Core logic.

Quick Quiz //

By default, does @dataclass generate __eq__ that compares by identity or by field values?


šŸš€ LEVEL UP TO SENIOR:Unlock 500+ Advanced Practical Challenges & Exercises.
šŸŽ“ COURSERA PARTNER:Earn professional Google, Meta, and IBM certificates to supercharge your resume.

Before dataclasses, a simple class for holding a few fields required writing __init__, __repr__, and __eq__ by hand, or reaching for a third-party library. The dataclasses module, part of the standard library since 3.7, generates all of that from type-annotated class attributes — and keeps getting more capable with each release.

1What @dataclass Generates For You

Writing a class that just holds a handful of related values used to mean typing out __init__ with a repetitive self.field = field line per attribute, then usually adding __repr__ for debuggability and __eq__ if you ever needed to compare two instances by value — three methods of boilerplate for what is conceptually just a named tuple of fields.

@dataclass, decorating a class whose body is just type-annotated attributes, generates all three by default: __init__ accepting each field as a parameter (in declaration order, with correct defaults), __repr__ producing a readable ClassName(field=value, ...) string, and __eq__ comparing all fields for equality between two instances of the same class. You opt out of any of them individually via decorator arguments (@dataclass(eq=False), for example) if you need custom behavior.

Critically, @dataclass reads the class's type annotations to know which attributes are fields — this is the same annotation syntax from the Type Hints lesson, now doing double duty as both documentation/static-checking metadata and the literal specification dataclasses uses to generate code. A class attribute without a type annotation is *not* treated as a dataclass field at all.

āœ•
—
+
class PointManual:
    def __init__(self, x: float, y: float):
        self.x = x
        self.y = y

from dataclasses import dataclass

@dataclass
class Point:
    x: float
    y: float
localhost:3000
Console Output
print(Point(1.0, 2.0))
Point(x=1.0, y=2.0)

2The Mutable Default Trap and field()

Python's def f(items=[]): mutable-default-argument pitfall — where the same list object is reused across every call that doesn't supply its own — has a direct analog in dataclasses, and @dataclass actively refuses to let you write items: list = [] as a field default, raising a ValueError at class-definition time rather than letting the bug slip through silently.

The fix is field(default_factory=list), imported from dataclasses alongside the decorator: instead of a single shared default value, you supply a zero-argument callable that's invoked fresh for every new instance. default_factory=list calls list() each time a ShoppingCart is constructed, guaranteeing every cart starts with its own independent empty list rather than all carts secretly sharing one.

field() also accepts default (for immutable defaults, equivalent to the plain = value syntax), repr=False and compare=False to exclude a specific field from the generated __repr__ or __eq__ (useful for internal caches or large binary blobs you don't want dumped in logs), and init=False for computed fields that shouldn't be constructor parameters at all.

āœ•
—
+
p1 = Point(1.0, 2.0)
p2 = Point(1.0, 2.0)
print(p1)          # Point(x=1.0, y=2.0)
print(p1 == p2)     # True — compares field values, not identity
localhost:3000
Runtime Behavior
cart_b.items after cart_a mutation
[] — independent, thanks to default_factory

3Frozen Instances and Structural Fit With match/case

@dataclass(frozen=True) makes every field read-only after __init__ runs — any attempt to reassign a field raises dataclasses.FrozenInstanceError — and as a side effect, also makes the instance hashable (assuming all fields are themselves hashable), so frozen dataclass instances can be used as dictionary keys or set members, something ordinary mutable dataclasses cannot do safely.

This maps directly onto a very common professional need: value objects. A Coordinate, a Money amount, an immutable Config snapshot — anything that represents a value rather than an entity with a lifecycle benefits from being frozen, since it prevents an entire class of bugs where some distant piece of code mutates a shared object that other code didn't expect to change.

Dataclasses also automatically populate __match_args__ from field declaration order, which is exactly what makes case Point(x, y): — positional class patterns from the previous lesson — work out of the box. Combined with frozen=True and Success | Failure-style discriminated unions, dataclasses are the standard-library foundation that Python's modern typing and pattern-matching features are built to work with directly.

āœ•
—
+
from dataclasses import dataclass, field

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

cart_a = ShoppingCart()
cart_a.items.append("apple")
cart_b = ShoppingCart()
print(cart_b.items)  # [] — NOT ['apple'], each gets its own list
localhost:3000
Immutability
c.lat = 0.0
dataclasses.FrozenInstanceError: cannot assign to field 'lat'

4Step-by-Step Breakdown

How many times have you written self.x = x, self.y = y, self.z = z in an __init__ just to hold three values? @dataclass ends that.

A plain class needs __init__ written by hand. @dataclass generates it from your annotated attributes instead.

You get a readable __repr__ and value-based __eq__ for free — no more comparing objects by identity when you meant by value.

Checkpoint: By default, does @dataclass generate __eq__ that compares by identity or by field values?

  • →By field values (two instances with equal fields are ==)
  • →By identity (same as the default object.__eq__)

field() gives you control over defaults for mutable types, since 'x: list = []' is a trap that shares one list across all instances.

Checkpoint: Why does items: list[str] = [] need field(default_factory=list) instead?

  • →A plain mutable default is shared across every instance of the class
  • →It is only a type-hinting syntax preference with no runtime effect

frozen=True makes instances immutable and hashable — perfect for values you never want mutated after creation, like config or coordinates used as dict keys.

Dataclasses are the natural home for constants and closed sets of values too — next, we cover Enums for exactly that.

Enforce Real Immutability. Finish check: frozen=True raises on attribute reassignment.

Level Up šŸš€

Advanced cheat sheets, SEO tricks, and interview prep for this topic.

Browser Support

ChromeSupported

Fully supported (via server-side Python execution).

FirefoxSupported

Fully supported (via server-side Python execution).

SafariSupported

Fully supported (via server-side Python execution).

EdgeSupported

Fully supported (via server-side Python execution).

Best Practices

Always use field(default_factory=...) for mutable field defaults

@dataclass raises a ValueError at class-definition time if you try a plain mutable literal default, but it's worth understanding why: it prevents the classic shared-mutable-default bug from ever reaching runtime.

Freeze value objects, leave entities with identity/lifecycle mutable

frozen=True is the right default for coordinates, money amounts, and config snapshots — things defined entirely by their values. Objects with a meaningful identity that changes over time (a User being edited) should stay mutable.

Frequent Bugs

THE BUG

Adding a field without a type annotation (just `count = 0`) and being confused when it is not included in the generated __init__, __repr__, or __eq__.

THE FIX

Every dataclass field must be annotated (`count: int = 0`). Unannotated class attributes are treated as plain class-level constants, not dataclass fields, and are silently excluded from all generated methods.

Real-World Examples

An Immutable Config Snapshot Passed Through a Pipeline

A data pipeline loads configuration once at startup and passes it through several processing stages; no stage should be able to accidentally mutate the shared config for downstream stages.

from dataclasses import dataclass

@dataclass(frozen=True)
class PipelineConfig:
    batch_size: int
    input_path: str
    retries: int = 3

def process(config: PipelineConfig) -> None:
    print(f"Processing with batch_size={config.batch_size}")

Interview Prep

?Frequently Asked Questions

Pascual Vila

Pascual Vila

Frontend Instructor // Code Syllabus

Common Pitfalls & Errors

The Error //

Declaring a mutable field default as a plain literal (tags: list = []) and hitting a ValueError: mutable default <class 'list'> for field tags is not allowed.

# Wrong: raises ValueError at class definition time @dataclass class Post: tags: list = [] # Correct from dataclasses import field @dataclass class Post: tags: list = field(default_factory=list)

The Solution //

Import field from dataclasses and use field(default_factory=list) (or dict, or set) instead of a literal mutable default.

Lesson Glossary

[01]@dataclass

A class decorator (dataclasses module) that generates __init__, __repr__, and __eq__ from a class's type-annotated attributes.

Code Preview
// @dataclass context

[02]field()

A dataclasses function used to customize a specific field's default value (via default_factory), and its inclusion in repr, eq, and init.

Code Preview
// field() context

[03]frozen dataclass

A dataclass created with @dataclass(frozen=True), whose fields cannot be reassigned after construction, and which becomes hashable as a result.

Code Preview
// frozen dataclass context

[04]__match_args__

A class attribute (auto-populated by dataclasses) listing field names in order, enabling positional class patterns in match/case.

Code Preview
// __match_args__ context

Continue Learning