W3docs

Python Dataclasses

Python Dataclasses erklärt: @dataclass-Dekorator, field()-Standardwerte, Sortierung, Unveränderlichkeit und Vererbung mit Beispielen.

Eine Dataclass ist eine reguläre Python-Klasse, deren Boilerplate — __init__, __repr__ und __eq__ — automatisch durch den @dataclass-Dekorator generiert wird. Das Ergebnis ist weniger Code, weniger Tippfehler und Klassen, die sofort lesbar sind.

Dieses Kapitel behandelt:

  • Warum Dataclasses existieren und wann man sie verwendet
  • Den @dataclass-Dekorator
  • Standardwerte für Felder und den field()-Helfer
  • Steuerung von Gleichheit und Sortierung
  • Unveränderliche Dataclasses mit frozen=True
  • Post-Initialisierungslogik mit __post_init__
  • Vererbung mit Dataclasses
  • Dataclasses vs. NamedTuple vs. einfachen Klassen

Bevor Sie dieses Kapitel lesen, sollten Sie sich mit Python-Klassen und -Objekten und Python-Vererbung vertraut gemacht haben.

Warum Dataclasses?

Stellen Sie sich eine Klasse vor, die ein Produkt in einem Online-Shop speichert. Ohne Dataclasses schreiben Sie die gleichen Attributzuweisungen dreimal — einmal in __init__, einmal in __repr__ und einmal in __eq__:

class Product:
    def __init__(self, name, price, stock):
        self.name = name
        self.price = price
        self.stock = stock

    def __repr__(self):
        return f"Product(name={self.name!r}, price={self.price}, stock={self.stock})"

    def __eq__(self, other):
        if not isinstance(other, Product):
            return NotImplemented
        return (self.name, self.price, self.stock) == (other.name, other.price, other.stock)

Der @dataclass-Dekorator generiert all das Obige aus einer einzigen annotierten Feldliste:

from dataclasses import dataclass

@dataclass
class Product:
    name: str
    price: float
    stock: int

Beide Versionen verhalten sich identisch. Die Dataclass-Version ist kürzer, schwieriger falsch zu machen, und vermittelt sofort, dass diese Klasse in erster Linie ein Datenbehälter ist.

Der @dataclass-Dekorator

Importieren Sie dataclass aus dem Standardbibliotheksmodul dataclasses und wenden Sie es auf Ihre Klasse an. Jedes Feld wird als typannotierte Klassenvariable deklariert:

from dataclasses import dataclass

@dataclass
class Point:
    x: float
    y: float

p = Point(1.5, 2.0)
print(p)          # Point(x=1.5, y=2.0)
print(p.x)        # 1.5

p2 = Point(1.5, 2.0)
print(p == p2)    # True  — __eq__ compares field by field

Der Dekorator generiert:

MethodeWas sie tut
__init__Akzeptiert jedes Feld als Parameter und weist es self zu
__repr__Gibt einen lesbaren string zurück, wie z. B. Point(x=1.5, y=2.0)
__eq__Vergleicht zwei Instanzen Feld für Feld

Typannotationen sind erforderlich, werden aber zur Laufzeit nicht durchgesetzt

Felddeklarationen erfordern eine Typannotation (x: float). Python überprüft den Typ zur Laufzeit nicht — Sie können trotzdem einen string übergeben, wo ein float erwartet wird. Die Annotation ist Metadaten, die von Typprüfern wie mypy und vom dataclasses-Mechanismus selbst verwendet werden. Zur Laufzeit-Typvalidierung siehe Python Type Hints.

Standardwerte

Weisen Sie einem Feld direkt einen Standardwert zu, um es in __init__ optional zu machen:

from dataclasses import dataclass

@dataclass
class Config:
    host: str = "localhost"
    port: int = 8080
    debug: bool = False

c1 = Config()
print(c1)   # Config(host='localhost', port=8080, debug=False)

c2 = Config(host="example.com", port=443)
print(c2)   # Config(host='example.com', port=443, debug=False)

Felder mit Standardwerten müssen nach Feldern ohne Standardwerte stehen — genau dieselbe Regel wie bei regulären Funktionsparametern.

Veränderliche Standardwerte und field()

Sie können kein veränderbares object (eine Liste, ein dict oder ein set) als einfachen Standardwert verwenden. Python würde eine Liste unter allen Instanzen teilen, was zu subtilen Fehlern führt:

from dataclasses import dataclass

# This raises a ValueError at class definition time:
# @dataclass
# class Bag:
#     items: list = []   # ValueError: mutable default is not allowed

Verwenden Sie stattdessen field(default_factory=...), um für jede Instanz ein frisches object zu erstellen:

from dataclasses import dataclass, field

@dataclass
class Bag:
    items: list = field(default_factory=list)

b1 = Bag()
b2 = Bag()
b1.items.append("apple")

print(b1.items)   # ['apple']
print(b2.items)   # []  — b2 has its own separate list

default_factory akzeptiert jeden nullargumentigen Callable, einschließlich Lambdas und eigener Funktionen.

Der field()-Helfer

field() gibt Ihnen feingranulare Kontrolle über einzelne Felder. Die nützlichsten Parameter sind:

ParameterZweck
defaultEin einfacher Standardwert (nur skalare Typen)
default_factoryEin Callable, der den Standardwert erzeugt
reprFalse, um dieses Feld aus __repr__ auszuschließen
compareFalse, um dieses Feld aus __eq__ (und der Sortierung) auszuschließen
initFalse, um dieses Feld aus __init__ auszuschließen
from dataclasses import dataclass, field
import time

@dataclass
class LogEntry:
    message: str
    level: str = "INFO"
    timestamp: float = field(default_factory=time.time, repr=False, compare=False)

entry = LogEntry("Server started")
print(entry)               # LogEntry(message='Server started', level='INFO')
# timestamp exists but is hidden from repr and ignored in comparisons
print(entry.timestamp > 0) # True

Sortierung

Standardmäßig unterstützen Dataclasses Gleichheit (==, !=), aber keine Sortierung (<, >, <=, >=). Aktivieren Sie die Sortierung, indem Sie order=True an den Dekorator übergeben:

from dataclasses import dataclass

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

v1 = Version(1, 2, 0)
v2 = Version(1, 3, 0)
v3 = Version(1, 2, 0)

print(v1 < v2)    # True
print(v1 == v3)   # True
print(v2 > v1)    # True

versions = [Version(2, 0, 0), Version(1, 9, 1), Version(1, 2, 3)]
print(sorted(versions))
# [Version(major=1, minor=2, patch=3),
#  Version(major=1, minor=9, patch=1),
#  Version(major=2, minor=0, patch=0)]

Python generiert die Vergleichsmethoden, indem es die Felder in der Reihenfolge ihrer Deklaration tupel-artig vergleicht. Sie können ein Feld mit field(compare=False) von Vergleichen ausschließen.

Unveränderliche Dataclasses mit frozen=True

Übergeben Sie frozen=True, um alle Felder nach der Erstellung schreibgeschützt zu machen. Jeder Versuch, ein Feld zu ändern, löst einen FrozenInstanceError aus:

from dataclasses import dataclass

@dataclass(frozen=True)
class Coordinate:
    lat: float
    lon: float

london = Coordinate(51.5074, -0.1278)
print(london)        # Coordinate(lat=51.5074, lon=-0.1278)

# london.lat = 0.0  # FrozenInstanceError: cannot assign to field 'lat'

Eingefrorene Dataclasses sind auch hashbar (sie implementieren __hash__), sodass Sie sie als Dictionary-Schlüssel oder Mitglieder von Sets verwenden können:

from dataclasses import dataclass

@dataclass(frozen=True)
class Coordinate:
    lat: float
    lon: float

cities = {
    Coordinate(51.5074, -0.1278): "London",
    Coordinate(48.8566,  2.3522): "Paris",
}
print(cities[Coordinate(51.5074, -0.1278)])   # London

Reguläre (veränderliche) Dataclasses sind standardmäßig nicht hashbar — Python setzt __hash__ auf None, wenn __eq__ ohne frozen=True definiert wird.

Post-Initialisierungslogik mit __post_init__

Manchmal müssen Sie den Wert eines Feldes aus anderen Feldern ableiten oder die Eingabe nach dem Ausführen von __init__ validieren. Definieren Sie eine __post_init__-Methode — sie wird automatisch am Ende des generierten __init__ aufgerufen:

from dataclasses import dataclass, field
import math

@dataclass
class Circle:
    radius: float

    def __post_init__(self):
        if self.radius <= 0:
            raise ValueError(f"radius must be positive, got {self.radius}")

    @property
    def area(self):
        return math.pi * self.radius ** 2

c = Circle(5)
print(round(c.area, 4))   # 78.5398

# Circle(-1)  # ValueError: radius must be positive, got -1

Sie können auch ein abgeleitetes Feld berechnen. Markieren Sie es mit field(init=False), damit es nicht in __init__ erscheint, und setzen Sie es dann innerhalb von __post_init__:

from dataclasses import dataclass, field

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

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

r = Rectangle(4, 6)
print(r)         # Rectangle(width=4, height=6, area=24)
print(r.area)    # 24

Vererbung mit Dataclasses

Eine Dataclass kann von einer anderen Dataclass erben. Das __init__ der Kindklasse enthält Felder aus beiden Klassen — Elternfelder zuerst, in der Reihenfolge ihrer Deklaration:

from dataclasses import dataclass

@dataclass
class Animal:
    name: str
    age: int

@dataclass
class Dog(Animal):
    breed: str

rex = Dog(name="Rex", age=3, breed="Labrador")
print(rex)    # Dog(name='Rex', age=3, breed='Labrador')

Achtung: Wenn eine Elternklasse ein Feld mit einem Standardwert hat, müssen alle Kindfelder ebenfalls Standardwerte haben. Dies ist dieselbe Regel, die für reguläre Python-Funktionssignaturen gilt — ein Parameter ohne Standardwert kann keinem mit Standardwert folgen.

from dataclasses import dataclass

@dataclass
class Animal:
    name: str
    age: int = 0   # has a default

# @dataclass
# class Dog(Animal):
#     breed: str   # TypeError: non-default argument 'breed' follows default argument

Umgehen Sie dies, indem Sie dem Kindfeld ebenfalls einen Standardwert geben oder die Hierarchie so umstrukturieren, dass Felder mit Standardwerten zuletzt kommen.

Dekoratorparameter auf einen Blick

@dataclass(
    init=True,     # generate __init__       (default True)
    repr=True,     # generate __repr__       (default True)
    eq=True,       # generate __eq__         (default True)
    order=False,   # generate <, >, <=, >=   (default False)
    frozen=False,  # make fields immutable   (default False)
)
class MyClass:
    ...

In den meisten Fällen müssen Sie die wenigsten dieser Parameter ändern. Die gängigsten sind order=True und frozen=True.

Hilfsfunktionen

Das dataclasses-Modul bietet auch drei praktische Funktionen:

fields()

Gibt ein Tupel von Field-Objekten zurück, die jedes Feld in der Klasse beschreiben:

from dataclasses import dataclass, fields

@dataclass
class Point:
    x: float
    y: float

for f in fields(Point):
    print(f.name, f.type)
# x <class 'float'>
# y <class 'float'>

asdict()

Konvertiert eine Dataclass-Instanz in ein einfaches Dictionary (rekursiv):

from dataclasses import dataclass, asdict

@dataclass
class Address:
    street: str
    city: str

@dataclass
class Person:
    name: str
    address: Address

p = Person("Alice", Address("10 Downing St", "London"))
print(asdict(p))
# {'name': 'Alice', 'address': {'street': '10 Downing St', 'city': 'London'}}

Dies ist nützlich beim Serialisieren in JSON oder beim Senden von Daten an eine API.

astuple()

Konvertiert in ein Tupel (rekursiv):

from dataclasses import dataclass, astuple

@dataclass
class Point:
    x: float
    y: float

p = Point(3.0, 4.0)
print(astuple(p))   # (3.0, 4.0)

Dataclasses vs. NamedTuple vs. einfache Klassen

MerkmalEinfache KlasseNamedTupledataclass
Auto __init__NeinJaJa
Auto __repr__NeinJaJa
Auto __eq__NeinJa (nach Wert)Ja (nach Wert)
VeränderlichJaNeinJa (Standard)
HashbarNein (wenn __eq__ definiert)JaNur mit frozen=True
SortierungManuellJaorder=True
VererbungJaEingeschränktJa
isinstance-PrüfungJaJa (auch tuple)Ja
Entpacken (a, b = obj)NeinJaNein

Verwenden Sie eine Dataclass, wenn:

  • Sie veränderliche Daten mit optionaler Unveränderlichkeit möchten.
  • Sie Vererbung oder Post-Init-Logik benötigen.
  • Sie feingranulare Feldkontrolle wünschen (field()).

Verwenden Sie NamedTuple, wenn:

  • Sie einen unveränderlichen Datensatz wünschen, der sich auch wie ein Tupel verhält (positionelles Entpacken, CSV-Zeilen).
  • Sie Kompatibilität mit Code benötigen, der Tupel erwartet.

Verwenden Sie eine einfache Klasse, wenn:

  • Die Klasse erhebliches Verhalten und sehr wenige reine Daten hat.
  • Sie ein benutzerdefiniertes __init__ benötigen, das sich nicht durch __post_init__ ausdrücken lässt.

Häufige Fallstricke

Veränderliche Standardwerte. Die Verwendung einer Liste oder eines dict als einfachen Standardwert löst zur Klassendefinitionszeit einen ValueError aus. Verwenden Sie immer field(default_factory=...).

Hashbarkeit. Reguläre Dataclasses sind nicht hashbar. Wenn Sie sie als dict-Schlüssel oder in Sets benötigen, verwenden Sie frozen=True oder übergeben Sie unsafe_hash=True (selten empfohlen).

eq=False. Wenn Sie die Gleichheitsgenerierung deaktivieren (eq=False), greift Python auf den Identitätsvergleich (is) zurück, was für Datenobjekte fast nie das Gewünschte ist.

Reihenfolge der geerbten Standardwerte. Wenn ein Elternfeld einen Standardwert hat und ein Kindfeld keinen, löst Python einen TypeError aus. Planen Sie die Feldreihenfolge in Ihrer Hierarchie sorgfältig.

Zusammenfassung

KonzeptWas es tut
@dataclassGeneriert __init__, __repr__, __eq__ automatisch
field()Feingranulare Feldkontrolle: Standardwerte, repr, compare, init
default_factoryLiefert einen frischen veränderbaren Standardwert für jede Instanz
order=TrueFügt <, >, <=, >= basierend auf der Feldreihenfolge hinzu
frozen=TrueMacht Felder schreibgeschützt und die Instanz hashbar
__post_init__Wird nach __init__ für Validierung oder abgeleitete Felder ausgeführt
fields()Gibt Metadaten über jedes Feld zurück
asdict()Konvertiert eine Instanz in ein einfaches dict (rekursiv)
astuple()Konvertiert eine Instanz in ein einfaches Tupel (rekursiv)

Verwandte Themen finden Sie unter Python-Klassen und -Objekte, Python-Vererbung und abstrakte Python-Basisklassen.

Übungen

Übung
Which decorator do you use to create a dataclass in Python?
Which decorator do you use to create a dataclass in Python?
Was this page helpful?