← 输入框|python.land|刷新

Python Data Class: A Better Way to Store Data

A Python data class is a regular Python class that has the @dataclass decorator. It is specifically created to hold data. Since Python version 3.7, Python offers data classes through a built-in module that you can import, called dataclasses. There are several advantages over regular Python classes, which we’ll explore in this article. We’ll also look at example code and some common operations you might want to perform with data classes.

The advantage of using data classes

Why should you use a data class instead of a regular Python class? First, let’s look at some of the advantages a Python data class has to offer.

Requires a minimal amount of code

The @dataclass decorator adds a lot of functionality to a class without adding any visible code. This makes your data class very compact while still offering many useful features. All you need to do is define the fields to hold your data. You don’t need to write an __init__ method or any other functions.

Comparison

Instances of Python data classes can be compared with == because the so-called dunder method __eq__ is implemented automatically. It compares the fields of both objects, in order. In general, we can compare any Python object that implements this special method to other objects of the same type.

Printing a data class

Similarly, because __repr__ is implemented, you can print data classes and get a nice representation of it. This is especially useful for debugging.

Data classes require type hints

Data classes are built around the type hints Python offers. Each field needs one; that’s how the decorator finds your fields. Using type hints reduces the chances of bugs and unexpected behavior in your code. You essentially declare the data type that should be stored in a variable.

Keep in mind that Python doesn’t check these types when your code runs. Card(rank=12) works fine, even if you declared rank as a str. Your IDE and type checkers like mypy will warn you, though.

Python data class example

Here’s an example of a data class at work:

from dataclasses import dataclass

@dataclass
class Card:
    rank: str
    suit: str

card1 = Card("Q", "hearts")
card2 = Card("Q", "hearts")

print(card1 == card2)
# True

print(card1.rank)
# Q

print(card1)
# Card(rank='Q', suit='hearts')Code language: Python (python)

Default values

A data class can have default values. Assigning default values is as simple as assigning a value to a variable. For example, to make our Card class have a default value of Queen of Hearts, we can do as follows:

from dataclasses import dataclass

@dataclass
class Card:
    rank: str = 'Q'
    suit: str = 'hearts'Code language: Python (python)

Fields with a default value must come after the fields without one, just like with function arguments.

Mutable defaults and default_factory

What if you want a field that starts as an empty list? Your first try probably looks like this:

from dataclasses import dataclass

@dataclass
class Hand:
    cards: list = []Code language: Python (python)

Python refuses to create this class:

ValueError: mutable default for field cards is not allowed: use default_factory

That’s a good thing. A default value is created only once, so every hand would share the same list, and adding a card to one hand would add it to all of them. The error message already tells you the fix: use field() with a default_factory. That’s a function that Python calls to create a fresh default for every new object:

from dataclasses import dataclass, field

@dataclass
class Hand:
    player: str
    cards: list[str] = field(default_factory=list)

alice = Hand('Alice')
bob = Hand('Bob')
alice.cards.append('Q of hearts')

print(alice)
# Hand(player='Alice', cards=['Q of hearts'])
print(bob)
# Hand(player='Bob', cards=[])Code language: Python (python)

The same goes for dictionaries and sets: use field(default_factory=dict) or field(default_factory=set).

Data class options

You can pass options to the decorator to change what it generates. These are the ones you’ll use most.

frozen=True: read-only data classes

With frozen=True, you can’t change the fields after the object is created. A frozen data class is also hashable, so you can use it as a dictionary key or put it in a set:

from dataclasses import dataclass

@dataclass(frozen=True)
class Card:
    rank: str
    suit: str

card = Card('Q', 'hearts')
seen = {card}
print(card in seen)
# True

card.rank = 'K'
# dataclasses.FrozenInstanceError: cannot assign to field 'rank'Code language: Python (python)

order=True: sorting data classes

By default, you can only compare data classes with ==. With order=True, you also get <, <=, > and >=. Python compares the fields one by one, in the order you defined them, so you can sort a list of objects:

from dataclasses import dataclass

@dataclass(order=True)
class Version:
    major: int
    minor: int
    patch: int

versions = [Version(3, 15, 2), Version(3, 9, 0), Version(3, 14, 1)]
print(sorted(versions))
# [Version(major=3, minor=9, patch=0), Version(major=3, minor=14, patch=1), Version(major=3, minor=15, patch=2)]Code language: Python (python)

slots=True and kw_only=True

Since Python 3.10, there are two more useful options:

  • slots=True gives your class slots. Objects use less memory, and assigning to a field that doesn’t exist raises an error instead of silently creating a new attribute.
  • kw_only=True makes all fields keyword-only. That makes calls easier to read when a class has many fields, and prevents mixing up arguments of the same type.
from dataclasses import dataclass

@dataclass(slots=True, kw_only=True)
class User:
    name: str
    email: str
    admin: bool = False

user = User(name='Erik', email='erik@example.com')
print(user)
# User(name='Erik', email='erik@example.com', admin=False)

User('Erik', 'erik@example.com')
# TypeError: User.__init__() takes 1 positional argument but 3 were givenCode language: Python (python)

Validation and computed fields with __post_init__

Since the decorator writes __init__ for you, where do you put extra setup code? In a method called __post_init__. The generated __init__ calls it right after it has set all fields. You can use it to validate the data or to calculate fields from other fields:

from dataclasses import dataclass, field

@dataclass
class Rectangle:
    width: float
    height: float
    area: float = field(init=False)

    def __post_init__(self):
        if self.width <= 0 or self.height <= 0:
            raise ValueError('width and height must be positive')
        self.area = self.width * self.height

print(Rectangle(3, 4))
# Rectangle(width=3, height=4, area=12)

Rectangle(-1, 4)
# ValueError: width and height must be positiveCode language: Python (python)

The field(init=False) tells the decorator that area is not an argument of __init__. It’s still a field, so it shows up when you print the object.

Converting a data class to JSON

A common use case is to convert your nicely structured data class to JSON. E.g., if you want to export the data to a database, or send it to the browser. For simple cases, the standard library has you covered. The asdict() function turns a data class into a dictionary, and json.dumps() turns that dictionary into JSON:

import json
from dataclasses import dataclass, asdict

@dataclass
class Card:
    rank: str = 'Q'
    suit: str = 'hearts'

card = Card()
print(json.dumps(asdict(card)))
# {"rank": "Q", "suit": "hearts"}Code language: Python (python)

This works as long as your fields contain types that JSON knows about: strings, numbers, booleans, None, lists, and dictionaries. asdict() also converts nested data classes. For other types, like a date, json.dumps() raises TypeError: Object of type date is not JSON serializable. A quick fix is to pass default=str, which converts anything unknown to a string:

import json
from dataclasses import dataclass, asdict
from datetime import date

@dataclass
class Event:
    name: str
    day: date

event = Event('PyCon', date(2026, 5, 13))
print(json.dumps(asdict(event), default=str))
# {"name": "PyCon", "day": "2026-05-13"}Code language: Python (python)

Converting back from JSON to a data class is where the standard library stops helping. If you need both directions, or you want the types checked, use a package. The most popular one is Pydantic. It has its own drop-in @dataclass decorator that validates and converts the data you pass in:

from datetime import date
from pydantic.dataclasses import dataclass
from pydantic import TypeAdapter

@dataclass
class Event:
    name: str
    day: date

event = Event('PyCon', '2026-05-13')
print(event)
# Event(name='PyCon', day=datetime.date(2026, 5, 13))

print(TypeAdapter(Event).dump_json(event).decode())
# {"name":"PyCon","day":"2026-05-13"}Code language: Python (python)

Note how Pydantic turned the string '2026-05-13' into a real date object.

A lighter alternative is a package called dataclasses-json. It requires an extra decorator. Like Pydantic, you’ll need to install it, preferably inside a virtual environment, with the pip install command or with uv:

$ python -m pip install dataclasses-json or, in a uv project: $ uv add dataclasses-json

Here’s an example of how you can use the package:

from dataclasses import dataclass
from dataclasses_json import dataclass_json

@dataclass_json
@dataclass
class Card:
    rank: str = 'Q'
    suit: str = 'hearts'

card = Card()
print(card.to_json())Code language: Python (python)

This results in the following output:

{“rank”: “Q”, “suit”: “hearts”}

Keep learning

1 thought on “Python Data Class: A Better Way to Store Data”

  1. There’s actually a simple built in way to convert a dataclass to JSON:

    
    >>> import json
    >>> json.dumps(card1.__dict__)
    '{"rank": "Q", "suit": "hearts"}'
    Code language: Python (python)

    And even if your data class has attributes that are not JSON serializable by default

    
    >>> import datetime
    >>> @dataclass
    ... class Card:
    ...     rank: str
    ...     suit: str
    ...     last_played: datetime.datetime
    ... 
    >>> card1 = Card("Q", "hearts", datetime.datetime.now())
    >>> json.dumps(card1.__dict__)
    Traceback ... TypeError: Object of type datetime is not JSON serializable
    >>> def dt_serializer(o):
    ...     if isinstance(o, datetime.datetime):
    ...         return o.__str__()
    ... 
    >>> json.dumps(card1.__dict__, default=dt_serializer)
    '{"rank": "Q", "suit": "hearts", "last_played": "2022-04-25 10:29:01.833211"}'
    Code language: Python (python)

    Also interesting to look at is:

    
    >>> card1.__annotations__
    {'sent': , 'suit': , 'rank': }
    Code language: Python (python)

Leave a Comment