Dennis Hendricks

Python dataclasses: Schluss mit dem Dict-Chaos

Samstag, 4. April 2026

Du kennst das. Irgendwo in deinem Code gibt es diese eine Funktion, die ein Dict zurückgibt. Erst hat es drei Keys. Dann fünf. Dann fragt dich jemand: “Was gibt result['meta'] denn zurück?” – und du musst die Funktion erst komplett lesen, um es rauszufinden.

Oder das andere Szenario: Du schreibst eine Klasse, die Daten hält. __init__, self.name = name, self.age = age, __repr__, __eq__… und du denkst: das kann doch nicht sein, dass ich das alles von Hand tippen muss.

dataclasses ist die Antwort auf beides.

Was ist ein Dataclass überhaupt?

Seit Python 3.7 gibt es das dataclasses-Modul in der Standardbibliothek. Die Idee ist simpel: Du beschreibst deine Datenstruktur mit Typ-Annotationen, und Python generiert automatisch den ganzen Boilerplate-Code für dich.

Statt:

class User:
    def __init__(self, name: str, age: int, email: str):
        self.name = name
        self.age = age
        self.email = email

    def __repr__(self):
        return f"User(name={self.name!r}, age={self.age!r}, email={self.email!r})"

    def __eq__(self, other):
        if not isinstance(other, User):
            return NotImplemented
        return (self.name, self.age, self.email) == (other.name, other.age, other.email)

Schreibst du:

from dataclasses import dataclass

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

Zwanzig Zeilen werden zu sechs. Und du bekommst __init__, __repr__ und __eq__ gratis dazu.

Standardwerte und das field()-Problem

Defaultwerte für einfache Typen funktionieren genau so, wie du es erwartest:

@dataclass
class User:
    name: str
    age: int
    email: str
    is_active: bool = True
    role: str = "viewer"

Aber Vorsicht bei mutablen Defaults wie Listen oder Dicts. Das kennt man aus normalen Python-Klassen auch: einen mutable Default-Wert direkt anzugeben führt zu geteiltem Zustand zwischen Instanzen. Bei Dataclasses wirft Python deshalb einen Fehler:

# Das schlägt fehl:
@dataclass
class Team:
    members: list = []  # ValueError!

Die Lösung ist field() mit default_factory:

from dataclasses import dataclass, field

@dataclass
class Team:
    members: list = field(default_factory=list)
    metadata: dict = field(default_factory=dict)

Jede neue Instanz bekommt ihre eigene frische Liste. Problem gelöst.

Felder ausblenden und konfigurieren

field() kann noch mehr. Zum Beispiel kannst du Felder aus dem __repr__ ausschließen – praktisch für Passwörter oder interne Zustände:

@dataclass
class User:
    name: str
    age: int
    password_hash: str = field(repr=False)

Oder du willst, dass ein Feld zwar im Objekt existiert, aber nicht im __init__ übergeben wird – etwa ein berechnetes Feld:

from dataclasses import dataclass, field

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

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

__post_init__ ist der Hook, der nach dem generierten __init__ aufgerufen wird. Ideal für Berechnungen, Validierungen oder alles, was du nach der Initialisierung noch erledigen willst.

Frozen Dataclasses: unveränderliche Daten

Manchmal willst du, dass ein Objekt nach der Erstellung nicht mehr verändert werden kann. Mit frozen=True macht Python das Objekt immutable:

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

Versuchst du danach p.x = 5, bekommst du einen FrozenInstanceError. Das ist besonders nützlich, wenn du Objekte als Dictionary-Keys oder in Sets verwenden willst – dafür müssen sie hashbar sein. Frozen Dataclasses sind automatisch hashbar.

Vergleich mit anderen Ansätzen

Kurz einordnen, wann was Sinn macht:

Dict: Gut für unstrukturierte oder dynamische Daten. Schlecht, wenn die Struktur feststeht – dann verlierst du Typ-Sicherheit und Autocomplete.

NamedTuple: Unveränderlich, tuple-kompatibel, etwas weniger Features als Dataclasses. Gut, wenn du tuple-Unpacking brauchst oder die Immutabilität zentral ist.

dataclass: Der Standard-Go-To für strukturierte Daten mit bekannten Feldern. Mutable by default, aber mit frozen=True auch unveränderlich. Unterstützt Vererbung, Methoden, field() – sehr flexibel.

Pydantic BaseModel: Wenn du Validierung und Serialisierung brauchst (z. B. für APIs). Mächtiger als Dataclasses, aber eine externe Abhängigkeit.

Für den Alltag gilt: Wenn du weißt, welche Felder eine Datenstruktur hat, nimm Dataclasses. Erst wenn du Validierungslogik brauchst, wechsel zu Pydantic.

Ein reales Beispiel

Statt:

def get_user_from_db(user_id: int) -> dict:
    # ...irgendwas mit der DB...
    return {
        "id": 42,
        "name": "Anna",
        "email": "anna@example.com",
        "roles": ["admin", "editor"],
        "created_at": "2024-01-15"
    }

Schreibst du:

from dataclasses import dataclass, field
from datetime import date

@dataclass
class User:
    id: int
    name: str
    email: str
    roles: list[str] = field(default_factory=list)
    created_at: date = field(default_factory=date.today)

def get_user_from_db(user_id: int) -> User:
    return User(id=42, name="Anna", email="anna@example.com", roles=["admin", "editor"])

Der Unterschied? Dein Editor weiß, was user.roles ist. Du bekommst Autocomplete. Tippfehler im Feldnamen werfen sofort einen AttributeError. Und in sechs Monaten, wenn du den Code wieder öffnest, verstehst du ihn sofort.

Kurz zusammengefasst

Python Dataclasses sind das richtige Tool, wenn du strukturierte Daten mit festen Feldern abbilden willst:

Weniger Boilerplate, mehr Struktur – und dein Kollege (oder du selbst in drei Monaten) wird es dir danken.

Und wenn du irgendwann Millionen dieser Objekte erzeugst und der RAM-Verbrauch zum Thema wird: Ab Python 3.10 gibt es @dataclass(slots=True) – das kombiniert den Komfort von Dataclasses mit dem Speichervorteil von __slots__.

Vorschaubild: Etienne Girardet, Unsplash