Writing boilerplate code for classes that mainly store data is tedious and error-prone. Dataclasses automatically generate init, repr, and eq methods from type-annotated fields, reducing code while maintaining full type safety and IDE support.

Why Use Dataclasses?

  • Less boilerplate code
  • Automatic __init__, __repr__, __eq__ generation
  • Type hints integration
  • Mutable by default, but can be frozen
  • Works with inheritance

Basic Dataclass

person1
basic.py
Replay: real traced execution (multi-file project)
# Basic dataclass definition

from dataclasses import dataclass

@dataclass
class Person:
    name: str
    age: int
    email: str

# Create instances - __init__ is auto-generated
person1 = Person("Alice", 30, "alice@example.com")
person2 = Person("Bob", 25, "bob@example.com")

# __repr__ is auto-generated
print(person1)

# __eq__ is auto-generated
print(person1 == person2)  # False
print(person1 == Person(person1.name, person1.age, person1.email))  # True
# Basic dataclass definition

from dataclasses import dataclass

@dataclass
class Person:
    name: str
    age: int
    email: str

# Create instances - __init__ is auto-generated
person1 = Person("Dana", 42, "dana@example.com")
person2 = Person("Bob", 25, "bob@example.com")

# __repr__ is auto-generated
print(person1)

# __eq__ is auto-generated
print(person1 == person2)  # False
print(person1 == Person(person1.name, person1.age, person1.email))  # True
# Basic dataclass definition

from dataclasses import dataclass

@dataclass
class Person:
    name: str
    age: int
    email: str

# Create instances - __init__ is auto-generated
person1 = Person("Sam", 19, "sam@example.com")
person2 = Person("Bob", 25, "bob@example.com")

# __repr__ is auto-generated
print(person1)

# __eq__ is auto-generated
print(person1 == person2)  # False
print(person1 == Person(person1.name, person1.age, person1.email))  # True
  1. person1 ← Person(name='Alice', age=30, email='alice@example.com')

    6class Person:7    name(empty): str8    age(empty): int9    email(empty): str1011# Create instances - __init__ is auto-generated12person1→ Person(name='Alice', age=30, email='alice@example.com') = Person("Alice", 30, "alice@example.com")13#@person1=Person("Dana", 42, "dana@example.com"), Person("Sam", 19, "sam@example.com")14person2→ Person(name='Bob', age=25, email='bob@example.com') = Person("Bob", 25, "bob@example.com")1516# __repr__ is auto-generated17print(person1Person(name='Alice', age=30, email='alice@example.com'))1819# __eq__ is auto-generated20print(person1Person(name='Alice', age=30, email='alice@example.com') == person2Person(name='Bob', age=25, email='bob@example.com'))  # False21print(person1Person(name='Alice', age=30, email='alice@example.com') == Person(person1.nameAlice, person1.age30, person1.emailalice@example.com))  # True
    outputPerson(name='Alice', age=30, email='alice@example.com')
  1. person1 ← Person(name='Dana', age=42, email='dana@example.com')

    6class Person:7    name(empty): str8    age(empty): int9    email(empty): str1011# Create instances - __init__ is auto-generated12person1→ Person(name='Dana', age=42, email='dana@example.com') = Person("Dana", 42, "dana@example.com")13person2→ Person(name='Bob', age=25, email='bob@example.com') = Person("Bob", 25, "bob@example.com")1415# __repr__ is auto-generated16print(person1Person(name='Dana', age=42, email='dana@example.com'))1718# __eq__ is auto-generated19print(person1Person(name='Dana', age=42, email='dana@example.com') == person2Person(name='Bob', age=25, email='bob@example.com'))  # False20print(person1Person(name='Dana', age=42, email='dana@example.com') == Person(person1.nameDana, person1.age42, person1.emaildana@example.com))  # True
    outputPerson(name='Dana', age=42, email='dana@example.com')
  1. person1 ← Person(name='Sam', age=19, email='sam@example.com')

    6class Person:7    name(empty): str8    age(empty): int9    email(empty): str1011# Create instances - __init__ is auto-generated12person1→ Person(name='Sam', age=19, email='sam@example.com') = Person("Sam", 19, "sam@example.com")13person2→ Person(name='Bob', age=25, email='bob@example.com') = Person("Bob", 25, "bob@example.com")1415# __repr__ is auto-generated16print(person1Person(name='Sam', age=19, email='sam@example.com'))1718# __eq__ is auto-generated19print(person1Person(name='Sam', age=19, email='sam@example.com') == person2Person(name='Bob', age=25, email='bob@example.com'))  # False20print(person1Person(name='Sam', age=19, email='sam@example.com') == Person(person1.nameSam, person1.age19, person1.emailsam@example.com))  # True
    outputPerson(name='Sam', age=19, email='sam@example.com')
dataclass A decorator that automatically generates special methods like __init__, __repr__, and __eq__ for classes that primarily store data, based on type-annotated class attributes.

Default Values

Fields can have default values, but fields with defaults must come after fields without.

default_values.py
Replay: real traced execution (multi-file project)
# Dataclass with default values

from dataclasses import dataclass

@dataclass
class Product:
    name: str
    price: float
    quantity: int = 0
    in_stock: bool = True
    category: str = "General"

# Use defaults
product1 = Product("Laptop", 999.99)
print(product1)

# Override defaults
product2 = Product("Mouse", 29.99, quantity=50, category="Electronics")
print(product2)

# Out of stock product
product3 = Product("Phone", 599.99, in_stock=False)
print(product3)
  1. quantity ← (empty), in_stock ← (empty), category ← (empty), product1 ← Product(name='Laptop', price=999.99, quantity=0, in_stock=True, category='General')

    6class Product:7    name(empty): str8    price(empty): float9    quantity→ (empty): int = 010    in_stock→ (empty): bool = TrueTrue11    category→ (empty): str = "General"1213# Use defaults14product1→ Product(name='Laptop', price=999.99, quantity=0, in_stock=True, category='General') = Product("Laptop", 999.99)15print(product1Product(name='Laptop', price=999.99, quantity=0, in_stock=True, category='General'))1617# Override defaults18product2→ Product(name='Mouse', price=29.99, quantity=50, in_stock=True, category='Electronics') = Product("Mouse", 29.99, quantity=50, category="Electronics")19print(product2Product(name='Mouse', price=29.99, quantity=50, in_stock=True, category='Electronics'))2021# Out of stock product22product3→ Product(name='Phone', price=599.99, quantity=0, in_stock=False, category='General') = Product("Phone", 599.99, in_stock=False)23print(product3Product(name='Phone', price=599.99, quantity=0, in_stock=False, category='General'))
    outputProduct(name='Laptop', price=999.99, quantity=0, in_stock=True, category='General')
    Product(name='Mouse', price=29.99, quantity=50, in_stock=True, category='Electronics')
    Product(name='Phone', price=599.99, quantity=0, in_stock=False, category='General')

Frozen Dataclass

Use frozen=True to create immutable instances that can be used as dictionary keys.

frozen.py
Replay: real traced execution (multi-file project)
# Frozen dataclasses (immutable)

from dataclasses import dataclass

@dataclass(frozen=True)
class Point:
    x: float
    y: float

# Create point
point = Point(10.5, 20.3)
print(f"Point: ({point.x}, {point.y})")

# Cannot modify - will raise FrozenInstanceError
try:
    point.x = 100
except AttributeError as e:
    print(f"Error: {e}")

# Can use as dictionary keys (hashable)
points_dict = {
    Point(0, 0): "origin",
    Point(10, 10): "ten-ten",
    Point(5, 5): "five-five"
}
print(points_dict[Point(0, 0)])
  1. point ← Point(x=10.5, y=20.3)

    6class Point:7    x(empty): float8    y(empty): float910# Create point11point→ Point(x=10.5, y=20.3) = Point(10.5, 20.3)12print(f"Point: ({point.x10.5}, {point.y20.3})")
    outputPoint: (10.5, 20.3)
  2. except AttributeError as e:

    16    point.x = 10017except AttributeError as e:18    print(f"Error: {ecannot assign to field 'x'}")
    outputError: cannot assign to field 'x'
  3. points_dict ← {Point(x=0, y=0): 'origin', Point(x=10, y=10): 'ten-ten', Point(x=5, y=5): 'five-five'}

    20# Can use as dictionary keys (hashable)21points_dict→ {Point(x=0, y=0): 'origin', Point(x=10, y=10): 'ten-ten', Point(x=5, y=5): 'five-five'} = {22    Point(0, 0): "origin",23    Point(10, 10): "ten-ten",24    Point(5, 5): "five-five"25}26print(points_dict{Point(x=0, y=0): 'origin', Point(x=10, y=10): 'ten-ten', Point(x=5, y=5): 'five-five'}[Point(0, 0)])
    outputorigin
frozen dataclass A dataclass with frozen=True that prevents attribute modification after creation, making instances immutable and hashable.

Field Function

Use field() for advanced options like mutable defaults or computed fields.

field_factory.py
Replay: real traced execution (multi-file project)
# Using field() for mutable defaults

from dataclasses import dataclass, field

@dataclass
class ShoppingCart:
    customer: str
    items: list = field(default_factory=list)
    total: float = 0.0

    def add_item(self, item: str, price: float):
        self.items.append(item)
        self.total += price

# Each cart gets its own list
cart1 = ShoppingCart("Alice")
cart1.add_item("Apple", 1.50)
cart1.add_item("Banana", 0.75)

cart2 = ShoppingCart("Bob")
cart2.add_item("Orange", 2.00)

print(f"{cart1.customer}'s cart: {cart1.items}, Total: ${cart1.total:.2f}")
print(f"{cart2.customer}'s cart: {cart2.items}, Total: ${cart2.total:.2f}")
  1. items ← (empty), total ← (empty), cart1 ← ShoppingCart(customer='Alice', items=[], total=0.0)

    6class ShoppingCart:7    customer(empty): str8    items→ (empty): list = field(default_factory=list)9    total→ (empty): float = 0.010    11    def add_item(self, item: str, price: float):12        self.items.append(item)13        self.total += price1415# Each cart gets its own list16cart1→ ShoppingCart(customer='Alice', items=[], total=0.0) = ShoppingCart("Alice")17cart1ShoppingCart(customer='Alice', items=[], total=0.0).add_item("Apple", 1.50)18cart1.add_item("Banana", 0.75)
  2. self.items ← ['Apple'], self.total ← 1.5

    pass 1 of 3
    11def add_item(selfShoppingCart(customer='Alice', items=[], total=0.0), itemApple: str, price1.5: float):12    self.items→ ['Apple'].append(itemApple)13    self.total→ 1.5 += price1.5
    All 3 passes — pass 1 is the card above
    passselfitempriceself.itemsself.total
    1ShoppingCart(customer='Alice', items=[], total=0.0)Apple1.5[] ['Apple']0.0 1.5
    2ShoppingCart(customer='Alice', items=['Apple'], total=1.5)Banana0.75['Apple'] ['Apple', 'Banana']1.5 2.25
    3ShoppingCart(customer='Bob', items=[], total=0.0)Orange2.0[] ['Orange']0.0 2.0
  3. cart1 ← ShoppingCart(customer='Alice', items=['Apple'], total=1.5)

    16cart1 = ShoppingCart("Alice")17cart1→ ShoppingCart(customer='Alice', items=['Apple'], total=1.5).add_item("Apple", 1.50)18cart1ShoppingCart(customer='Alice', items=['Apple'], total=1.5).add_item("Banana", 0.75)
  4. cart1 ← ShoppingCart(customer='Alice', items=['Apple', 'Banana'], total=2.25)

    17cart1.add_item("Apple", 1.50)18cart1→ ShoppingCart(customer='Alice', items=['Apple', 'Banana'], total=2.25).add_item("Banana", 0.75)1920cart2→ ShoppingCart(customer='Bob', items=[], total=0.0) = ShoppingCart("Bob")21cart2ShoppingCart(customer='Bob', items=[], total=0.0).add_item("Orange", 2.00)
  5. cart2 ← ShoppingCart(customer='Bob', items=['Orange'], total=2.0)

    20cart2 = ShoppingCart("Bob")21cart2→ ShoppingCart(customer='Bob', items=['Orange'], total=2.0).add_item("Orange", 2.00)2223print(f"{cart1.customerAlice}'s cart: {cart1.items['Apple', 'Banana']}, Total: ${cart1.total2.25:.2f}")24print(f"{cart2.customerBob}'s cart: {cart2.items['Orange']}, Total: ${cart2.total2.0:.2f}")
    outputAlice's cart: ['Apple', 'Banana'], Total: $2.25
    Bob's cart: ['Orange'], Total: $2.00
field() A function that provides fine-grained control over dataclass fields, including default_factory for mutable defaults and compare/hash options.

Comparison and Ordering

Use order=True to enable comparison operators based on field values.

ordering.py
Replay: real traced execution (multi-file project)
# Dataclass with ordering

from dataclasses import dataclass, field

@dataclass(order=True)
class Score:
    value: int
    player: str = field(compare=False)  # Don't use in comparison

# Create scores
scores = [
    Score(85, "Alice"),
    Score(92, "Bob"),
    Score(78, "Charlie"),
    Score(95, "David")
]

# Can compare
print(f"Bob's score > Alice's score: {scores[1] > scores[0]}")

# Can sort
sorted_scores = sorted(scores, reverse=True)
print("\nLeaderboard:")
for rank, score in enumerate(sorted_scores, 1):
    print(f"{rank}. {score.player}: {score.value}")
  1. player ← (empty), scores ← [Score(value=85, player='Alice'), Score(value=92, player='Bob'), Score(value=78, player='Charlie'), Score(value=95, player='David')]

    6class Score:7    value(empty): int8    player→ (empty): str = field(compare=False)  # Don't use in comparison910# Create scores11scores→ [Score(value=85, player='Alice'), Score(value=92, player='Bob'), Score(value=78, player='Charlie'), Score(value=95, player='David')] = [12    Score(85, "Alice"),13    Score(92, "Bob"),14    Score(78, "Charlie"),15    Score(95, "David")16]1718# Can compare19print(f"Bob's score > Alice's score: {scores[1]Score(value=92, player='Bob') > scores[0]Score(value=85, player='Alice')}")2021# Can sort22sorted_scores→ [Score(value=95, player='David'), Score(value=92, player='Bob'), Score(value=85, player='Alice'), Score(value=78, player='Charlie')] = sorted(scores[Score(value=85, player='Alice'), Score(value=92, player='Bob'), Score(value=78, player='Charlie'), Score(value=95, player='David')], reverse=True)23print("\nLeaderboard:")24for rank, score in enumerate(sorted_scores, 1):
    outputBob's score > Alice's score: True
    
    Leaderboard:
  2. for rank, score in enumerate(sorted_scores, 1):

    pass 1 of 4
    23print("\nLeaderboard:")24for rank1, scoreScore(value=95, player='David') in enumerate(sorted_scores[Score(value=95, player='David'), Score(value=92, player='Bob'), Score(value=85, player='Alice'), Score(value=78, player='Charlie')], 1):25    print(f"{rank1}. {score.playerDavid}: {score.value95}")
    output1. David: 95
    All 4 passes — pass 1 is the card above
    passrankscorescore.playerscore.value
    11Score(value=95, player='David')David95
    22Score(value=92, player='Bob')Bob92
    33Score(value=85, player='Alice')Alice85
    44Score(value=78, player='Charlie')Charlie78

@seealso namedtuple_intro "NamedTuples for immutable data" @seealso typing_intro "Type hints"

Exercise: practical.py

Create an AppConfig dataclass with feature toggles and configuration management methods