Python & AI Tutorials Logo
Python Programmierung

33. Data Classes für einfache strukturierte Daten

In Kapitel 30 haben wir gelernt, wie man Klassen erstellt, um unsere eigenen Typen zu definieren. Wir haben __init__-Methoden geschrieben, um Instanzen zu initialisieren, __repr__-Methoden, um sie anzuzeigen, und __eq__-Methoden, um sie zu vergleichen. Obwohl dieser Ansatz perfekt funktioniert, bedeutet das, dass Sie viel sich wiederholenden Code schreiben müssen, besonders wenn eine Klasse in erster Linie dazu existiert, Daten zu speichern.

Pythons Data Classes bieten eine sauberere, prägnantere Möglichkeit, Klassen zu erstellen, die hauptsächlich Container für Daten sind. Durch die Verwendung des @dataclass-Dekorators generiert Python automatisch gängige Methoden wie __init__, __repr__ und __eq__ basierend auf den Klassenattributen, die Sie definieren. Das reduziert Boilerplate-Code und macht Ihre Absichten klarer.

33.1) Was Data Classes sind und wann Sie sie verwenden sollten

Eine Data Class ist eine Klasse, die primär dafür entworfen ist, Datenwerte zu speichern. Anstatt Initialisierungs- und Vergleichsmethoden manuell zu schreiben, definieren Sie die Attribute, die Ihre Klasse haben soll, und Python generiert die notwendigen Methoden automatisch.

Warum Data Classes wichtig sind

Betrachten Sie eine normale Klasse, um ein Buch darzustellen:

python
class Book:
    def __init__(self, title, author, year):
        self.title = title
        self.author = author
        self.year = year
    
    def __repr__(self):
        return f"Book(title={self.title!r}, author={self.author!r}, year={self.year})"
    
    def __eq__(self, other):
        if not isinstance(other, Book):
            return False
        return (self.title == other.title and 
                self.author == other.author and 
                self.year == other.year)
 
book1 = Book("1984", "George Orwell", 1949)
print(book1)  # Output: Book(title='1984', author='George Orwell', year=1949)
 
book2 = Book("1984", "George Orwell", 1949)
print(book1 == book2)  # Output: True

Das funktioniert, aber beachten Sie, wie viel Code wir geschrieben haben, nur um drei Informationsstücke zu speichern. Die Methoden __init__, __repr__ und __eq__ folgen vorhersehbaren Mustern—sie behandeln einfach die Attribute, die wir definiert haben.

Data Classes beseitigen diese Wiederholung. Sie sind besonders nützlich, wenn:

  • Ihre Klasse primär Daten speichert statt komplexes Verhalten zu implementieren
  • Sie Standardmethoden benötigen wie Initialisierung, String-Repräsentation und Gleichheitsvergleich
  • Sie klareren, besser wartbaren Code möchten mit weniger Boilerplate
  • Sie Konfigurationsobjekte, Data-Transfer-Objekte oder einfache Records erstellen

Data Classes ersetzen normale Klassen nicht—sie ergänzen sie. Verwenden Sie normale Klassen, wenn Sie eine benutzerdefinierte Initialisierungslogik, komplexe Methoden oder Vererbungshierarchien benötigen. Verwenden Sie Data Classes, wenn Sie hauptsächlich einen strukturierten Container für zusammengehörige Daten brauchen.

Die Beziehung zwischen Data Classes und normalen Klassen

Data Classes sind immer noch normale Python-Klassen. Sie unterstützen alle Features, die wir in den Kapiteln 30-32 gelernt haben: Methoden, Properties, Vererbung und spezielle Methoden. Der @dataclass-Dekorator automatisiert lediglich die Erstellung gängiger Methoden und erspart Ihnen das Schreiben von wiederholendem Code.

Normale Klasse

Manuelles init

Manuelles repr

Manuelles eq

Benutzerdefinierte Methoden

Data Class

@dataclass-Dekorator

Automatisch generiertes init

Automatisch generiertes repr

Automatisch generiertes eq

Benutzerdefinierte Methoden

33.2) Data Classes mit @dataclass erstellen

Um eine Data Class zu erstellen, importieren Sie den dataclass-Dekorator aus dem Modul dataclasses und wenden ihn auf Ihre Klassendefinition an. Innerhalb der Klasse definieren Sie Klassenattribute mit Typannotationen (type annotations), die angeben, welche Daten die Klasse enthalten soll.

Grundlegende Syntax einer Data Class

python
from dataclasses import dataclass
 
@dataclass
class Student:
    name: str
    student_id: int
    gpa: float
 
# Instanzen erstellen
alice = Student("Alice Johnson", 12345, 3.8)
bob = Student("Bob Smith", 12346, 3.5)
 
print(alice)  # Output: Student(name='Alice Johnson', student_id=12345, gpa=3.8)
print(bob)    # Output: Student(name='Bob Smith', student_id=12346, gpa=3.5)

Schauen wir uns an, was der @dataclass-Dekorator macht:

  1. @dataclass: Das Anwenden dieses Dekorators sorgt dafür, dass Python die Methoden __init__, __repr__ und __eq__ automatisch für Sie schreibt

  2. Automatisches __init__: Python erstellt eine Initialisierungsmethode, die diese drei Parameter in der Reihenfolge akzeptiert, in der sie definiert wurden, und sie Instanzattributen zuweist

  3. Automatisches __repr__: Python erstellt eine String-Repräsentation, die den Klassennamen und alle Attributwerte zeigt

  4. Automatisches __eq__: Python erstellt eine Methode für den Gleichheitsvergleich, die alle Attribute vergleicht

  5. Wandelt Typannotationen in Instanzattribute um: In einer normalen Klasse erzeugt das Schreiben von name: str im Klassenrumpf ein Klassenattribut. Aber der @dataclass-Dekorator ändert dieses Verhalten—er verwendet diese Typannotationen, um stattdessen Instanzattribute zu definieren. Jede Instanz erhält ihre eigenen Attribute name, student_id und gpa.

Der zentrale Unterschied zu normalen Klassen:

python
# Normale Klasse – das sind Klassenattribute (von allen Instanzen geteilt)
class RegularStudent:
    name: str
    student_id: int
 
# Data Class – daraus werden Instanzattribute (jede Instanz hat ihre eigenen)
@dataclass
class DataStudent:
    name: str
    student_id: int

Typannotationen in Data Classes verstehen

In Data Classes definieren Typannotationen die Attribute und dokumentieren ihre erwarteten Typen:

python
from dataclasses import dataclass
 
@dataclass
class Product:
    name: str
    price: float
    in_stock: bool
 
# Die korrekten Typen wie dokumentiert verwenden
laptop = Product("Laptop", 999.99, True)
print(laptop)  # Output: Product(name='Laptop', price=999.99, in_stock=True)
 
# Python erzwingt keine Typen – das läuft ohne Fehler
macbook = Product("Macbook", "expensive", True)
print(macbook)  # Output: Product(name='Macbook', price='expensive', in_stock=True)
 
# Aber falsche Typen werden später Probleme verursachen:
discounted = laptop.price * 0.9     # Works: 899.991
discounted = macbook.price * 0.9    # TypeError: can't multiply sequence by non-int of type 'float'
 
tax = laptop.price + 50             # Works: 1049.99
tax = macbook.price + 50            # TypeError: can only concatenate str (not "int") to str

Python wird Sie nicht daran hindern, beim Erstellen einer Data-Class-Instanz falsche Typen zu übergeben. Die Typannotationen sind in erster Linie Dokumentation—sie sagen anderen Programmierern (und Type-Checking-Tools wie mypy), welche Typen Sie erwarten, aber Python erzwingt sie zur Laufzeit nicht. Das ist konsistent mit Pythons Philosophie der dynamischen Typisierung.

Allerdings macht das Befolgen der Typannotationen Ihren Code vorhersagbarer und leichter zu debuggen. Wenn Sie die falschen Typen verwenden, treten Fehler später auf, wenn Sie versuchen, die Daten zu verwenden, wodurch Bugs schwieriger nachzuverfolgen sind. Type-Checking-Tools können diese Abweichungen erkennen, bevor Sie Ihren Code ausführen, und helfen Ihnen, Probleme früh zu finden.

Auf Attribute zugreifen und Attribute ändern

Data-Class-Instanzen funktionieren genau wie normale Klasseninstanzen. Sie greifen über die Punktnotation auf Attribute zu und ändern sie:

python
from dataclasses import dataclass
 
@dataclass
class Employee:
    name: str
    position: str
    salary: float
 
emp = Employee("Sarah Chen", "Software Engineer", 95000.0)
 
# Auf Attribute zugreifen
print(emp.name)      # Output: Sarah Chen
print(emp.position)  # Output: Software Engineer
 
# Attribute ändern
emp.salary = 100000.0
emp.position = "Senior Software Engineer"
 
print(emp)  # Output: Employee(name='Sarah Chen', position='Senior Software Engineer', salary=100000.0)

Data Classes sind standardmäßig veränderlich (mutable)—Sie können ihre Attribute nach dem Erstellen ändern. Das unterscheidet sie von Tupeln oder Named Tuples, die unveränderlich sind. Wenn Sie Unveränderlichkeit benötigen, können Sie die Data Class mit frozen=True konfigurieren (das untersuchen wir in Abschnitt 33.4).

33.3) Generierte Methoden: __init__, __repr__ und __eq__

Der @dataclass-Dekorator generiert automatisch drei wesentliche Methoden. Wenn Sie verstehen, was diese Methoden tun, können Sie Data Classes effektiv nutzen und wissen, wann Sie sie anpassen sollten.

Die generierte __init__-Methode

Die __init__-Methode initialisiert eine neue Instanz mit den bereitgestellten Werten. Python generiert sie basierend auf der Reihenfolge Ihrer Attributdefinitionen:

python
from dataclasses import dataclass
 
@dataclass
class Rectangle:
    width: float
    height: float
 
# Die generierte __init__ akzeptiert width und height in dieser Reihenfolge
rect = Rectangle(10.5, 5.0)
print(rect.width)   # Output: 10.5
print(rect.height)  # Output: 5.0
 
# Sie können auch Keyword-Argumente verwenden
rect2 = Rectangle(height=8.0, width=12.0)
print(rect2.width)   # Output: 12.0
print(rect2.height)  # Output: 8.0

Die generierte __init__ ist äquivalent dazu, zu schreiben:

python
def __init__(self, width: float, height: float):
    self.width = width
    self.height = height

Diese automatische Generierung erspart Ihnen das Schreiben von wiederholendem Initialisierungscode, besonders bei Klassen mit vielen Attributen.

Die generierte __repr__-Methode

Die __repr__-Methode liefert eine String-Repräsentation der Instanz, die alle Attributwerte zeigt. Das ist unschätzbar für Debugging und Logging:

python
from dataclasses import dataclass
 
@dataclass
class Point:
    x: float
    y: float
    label: str
 
point = Point(3.5, 7.2, "A")
print(point)  # Output: Point(x=3.5, y=7.2, label='A')
print(repr(point))  # Output: Point(x=3.5, y=7.2, label='A')

Die generierte __repr__ folgt der Konvention, den Klassennamen und alle Attribute in einem Format zu zeigen, das verwendet werden könnte, um das Objekt erneut zu erstellen. Das ist viel hilfreicher als die Standardrepräsentation, die Sie ohne __repr__ erhalten würden: <__main__.Point object at 0x...>.

Die generierte __eq__-Methode

Die __eq__-Methode ermöglicht den Gleichheitsvergleich zwischen Instanzen. Zwei Data-Class-Instanzen gelten als gleich, wenn alle ihre entsprechenden Attribute gleich sind:

python
from dataclasses import dataclass
 
@dataclass
class Color:
    red: int
    green: int
    blue: int
 
color1 = Color(255, 0, 0)
color2 = Color(255, 0, 0)
color3 = Color(0, 255, 0)
 
print(color1 == color2)  # Output: True (same RGB values)
print(color1 == color3)  # Output: False (different RGB values)
print(color1 is color2)  # Output: False (different objects in memory)

Dieser automatische Gleichheitsvergleich basiert auf Wertgleichheit (value equality), nicht auf Identität. Obwohl color1 und color2 unterschiedliche Objekte im Speicher sind (wie durch is gezeigt), gelten sie als gleich, weil ihre Attribute übereinstimmen.

Die generierte __eq__-Methode vergleicht Attribute in der Reihenfolge, in der sie definiert sind:

python
from dataclasses import dataclass
 
@dataclass
class Book:
    title: str
    author: str
    year: int
 
book1 = Book("1984", "George Orwell", 1949)
book2 = Book("1984", "George Orwell", 1949)
book3 = Book("Animal Farm", "George Orwell", 1945)
 
print(book1 == book2)  # Output: True (all attributes match)
print(book1 == book3)  # Output: False (title and year differ)
 
# Vergleich mit Nicht-Book-Objekten gibt False zurück
print(book1 == "1984")  # Output: False
print(book1 == None)    # Output: False

Generierte Methoden mit manueller Implementierung vergleichen

Um zu würdigen, was Data Classes bieten, vergleichen wir die Data-Class-Version mit einer manuellen Implementierung:

python
from dataclasses import dataclass
 
# Data-Class-Version (kurz)
@dataclass
class PersonData:
    first_name: str
    last_name: str
    age: int
 
# Entsprechende manuelle Version (ausführlich)
class PersonManual:
    def __init__(self, first_name: str, last_name: str, age: int):
        self.first_name = first_name
        self.last_name = last_name
        self.age = age
    
    def __repr__(self):
        return f"PersonManual(first_name={self.first_name!r}, last_name={self.last_name!r}, age={self.age})"
    
    def __eq__(self, other):
        if not isinstance(other, PersonManual):
            return False
        return (self.first_name == other.first_name and
                self.last_name == other.last_name and
                self.age == other.age)
 
# Beide funktionieren identisch
p1 = PersonData("Alice", "Johnson", 30)
p2 = PersonManual("Alice", "Johnson", 30)
 
print(p1)  # Output: PersonData(first_name='Alice', last_name='Johnson', age=30)
print(p2)  # Output: PersonManual(first_name='Alice', last_name='Johnson', age=30)

Die Data-Class-Version erreicht dieselbe Funktionalität mit deutlich weniger Code. Diese Reduzierung von Boilerplate macht Ihren Code leichter zu lesen, zu warten und zu verändern.

Benutzerdefinierte Methoden zu Data Classes hinzufügen

Data Classes können benutzerdefinierte Methoden haben, genau wie normale Klassen. Der @dataclass-Dekorator generiert nur die Methoden für Initialisierung, Repräsentation und Gleichheit—Sie können beliebige weitere Funktionalität hinzufügen:

python
from dataclasses import dataclass
 
@dataclass
class Temperature:
    celsius: float
    
    def to_fahrenheit(self):
        """Convert temperature to Fahrenheit."""
        return (self.celsius * 9/5) + 32
    
    def to_kelvin(self):
        """Convert temperature to Kelvin."""
        return self.celsius + 273.15
    
    def is_freezing(self):
        """Check if temperature is at or below freezing point."""
        return self.celsius <= 0
 
temp = Temperature(25.0)
print(temp)  # Output: Temperature(celsius=25.0)
print(f"{temp.celsius}°C = {temp.to_fahrenheit()}°F")  # Output: 25.0°C = 77.0°F
print(f"Kelvin: {temp.to_kelvin()}")  # Output: Kelvin: 298.15
print(f"Freezing: {temp.is_freezing()}")  # Output: Freezing: False
 
cold_temp = Temperature(-5.0)
print(f"Freezing: {cold_temp.is_freezing()}")  # Output: Freezing: True

Data Classes übernehmen die wiederholenden Teile (Initialisierung, Repräsentation und Vergleich), während Sie benutzerdefinierte Methoden für Ihre spezifischen Anforderungen hinzufügen können, wie oben mit den Methoden zur Temperaturumrechnung gezeigt.

33.4) Standardwerte und Field-Optionen

Data Classes unterstützen Standardwerte für Attribute, sodass Sie Instanzen erstellen können, ohne jeden Parameter anzugeben. Sie können auch die Funktion field() verwenden, um erweiterte Verhaltensweisen zu konfigurieren, wie etwa Attribute von Vergleichen auszuschließen oder zu steuern, wie sie in der String-Repräsentation erscheinen.

Standardwerte bereitstellen

Sie können Standardwerte direkt in der Klassendefinition den Attributen zuweisen. Attribute mit Standardwerten müssen nach Attributen ohne Standardwerte kommen:

python
from dataclasses import dataclass
 
@dataclass
class User:
    username: str
    email: str
    is_active: bool = True  # Standardwert
    role: str = "user"      # Standardwert
 
# Instanzen mit und ohne Standardwerte erstellen
user1 = User("alice", "alice@example.com")
print(user1)  # Output: User(username='alice', email='alice@example.com', is_active=True, role='user')
 
user2 = User("bob", "bob@example.com", False, "admin")
print(user2)  # Output: User(username='bob', email='bob@example.com', is_active=False, role='admin')
 
# Keyword-Argumente verwenden, um bestimmte Standardwerte zu überschreiben
user3 = User("charlie", "charlie@example.com", role="moderator")
print(user3)  # Output: User(username='charlie', email='charlie@example.com', is_active=True, role='moderator')

Die Reihenfolgeregel (Attribute ohne Standardwerte vor Attributen mit Standardwerten) verhindert Mehrdeutigkeit in der generierten __init__-Methode. Das ist dieselbe Anforderung wie bei Funktionsparametern mit Standardwerten, die wir in Kapitel 20 gelernt haben.

Veränderliche Standardwerte und warum sie nicht erlaubt sind

Data Classes schützen Sie vor einem häufigen Fehler mit veränderlichen Standardwerten. Wenn Sie versuchen, ein veränderliches Objekt wie eine Liste oder ein Dictionary direkt als Standard zu verwenden, erhalten Sie einen Fehler:

python
from dataclasses import dataclass
 
# Das wird einen Fehler auslösen
@dataclass
class ShoppingCart:
    customer: str
    items: list = []  # ValueError: mutable default <class 'list'> for field items is not allowed: use default_factory

Dieser Fehler verhindert dasselbe Problem, das wir bei Standardargumenten von Funktionen in Kapitel 20 gesehen haben, bei dem alle Instanzen dasselbe veränderliche Objekt teilen würden.

field() mit default_factory für veränderliche Standardwerte verwenden

Die Lösung ist, die Funktion field() mit default_factory zu verwenden, die für jede Instanz einen neuen Standardwert erstellt:

python
from dataclasses import dataclass, field
 
@dataclass
class ShoppingCart:
    customer: str
    items: list = field(default_factory=list)  # Richtig: Neue Liste pro Instanz
 
# Jetzt erhält jede Instanz ihre eigene Liste
cart1 = ShoppingCart("Alice")
cart1.items.append("Book")
print(cart1.items)  # Output: ['Book']
 
cart2 = ShoppingCart("Bob")
print(cart2.items)  # Output: [] - Bob hat eine leere Liste
 
cart2.items.append("Laptop")
print(cart1.items)  # Output: ['Book'] - Alices Warenkorb bleibt unverändert
print(cart2.items)  # Output: ['Laptop'] - Bobs Warenkorb ist unabhängig

Der Parameter default_factory nimmt eine Funktion (wie list, dict oder set), die aufgerufen wird, um jedes Mal einen neuen Standardwert zu erstellen, wenn Sie eine Instanz erzeugen, ohne dieses Attribut anzugeben. Zum Beispiel bedeutet default_factory=list, dass Python list() aufruft, um für jede Instanz eine neue leere Liste zu erzeugen.

Felder vom Vergleich ausschließen

Manchmal möchten Sie bestimmte Attribute von Gleichheitsvergleichen ausschließen. Verwenden Sie dafür field(compare=False):

python
from dataclasses import dataclass, field
from datetime import datetime
 
@dataclass
class LogEntry:
    message: str
    level: str
    timestamp: datetime = field(compare=False)  # Timestamps nicht vergleichen
 
# Zwei Log-Einträge mit derselben Nachricht, aber unterschiedlichen Zeiten erstellen
entry1 = LogEntry("User logged in", "INFO", datetime(2024, 1, 15, 10, 30))
entry2 = LogEntry("User logged in", "INFO", datetime(2024, 1, 15, 10, 35))
 
# Sie sind gleich, weil timestamp vom Vergleich ausgeschlossen ist
print(entry1 == entry2)  # Output: True
 
# Aber sie haben unterschiedliche Timestamps
print(entry1.timestamp)  # Output: 2024-01-15 10:30:00
print(entry2.timestamp)  # Output: 2024-01-15 10:35:00

Das ist nützlich, wenn Sie Metadatenfelder (wie Timestamps, IDs oder interne Zähler) haben, die nicht beeinflussen sollen, ob zwei Instanzen als gleich angesehen werden.

Felder von der Repräsentation ausschließen

Sie können Felder auch von der String-Repräsentation ausschließen, indem Sie field(repr=False) verwenden:

python
from dataclasses import dataclass, field
 
@dataclass
class Account:
    username: str
    email: str
    password: str = field(repr=False)  # Passwort nicht in repr anzeigen
 
account = Account("alice", "alice@example.com", "secret123")
print(account)  # Output: Account(username='alice', email='alice@example.com')
# Das Passwort wird nicht angezeigt, ist aber weiterhin gespeichert
print(account.password)  # Output: secret123

Das ist besonders nützlich für sensible Daten wie Passwörter, API-Keys oder große Datenstrukturen, die die Darstellung überladen würden.

Data Classes mit frozen=True unveränderlich machen

Standardmäßig sind Data-Class-Instanzen veränderlich—Sie können ihre Attribute nach dem Erstellen ändern. Wenn Sie unveränderliche Instanzen (wie Tupel) möchten, verwenden Sie frozen=True:

python
from dataclasses import dataclass
 
@dataclass(frozen=True)
class Point:
    x: float
    y: float
 
point = Point(3.0, 4.0)
print(point)  # Output: Point(x=3.0, y=4.0)
 
# Ein Änderungsversuch löst einen Fehler aus
try:
    point.x = 5.0
except AttributeError as e:
    print(f"Error: {e}")  # Output: Error: cannot assign to field 'x'

Frozen Data Classes sind nützlich, wenn Sie Datenintegrität sicherstellen oder Instanzen als Dictionary-Schlüssel verwenden möchten (da Dictionary-Schlüssel unveränderlich sein müssen). Wenn eine Data Class frozen ist, generiert Python außerdem eine __hash__-Methode, wodurch Instanzen hashbar werden:

python
from dataclasses import dataclass
 
@dataclass(frozen=True)
class Coordinate:
    latitude: float
    longitude: float
 
# Frozen-Instanzen können Dictionary-Schlüssel sein
locations = {
    Coordinate(40.7128, -74.0060): "New York",
    Coordinate(51.5074, -0.1278): "London",
    Coordinate(35.6762, 139.6503): "Tokyo"
}
 
nyc = Coordinate(40.7128, -74.0060)
print(locations[nyc])  # Output: New York

33.5) Benutzerdefinierte Initialisierung mit __post_init__

Manchmal müssen Sie nach dem Ausführen der generierten __init__-Methode zusätzliche Einrichtungsschritte durchführen. Die Methode __post_init__ wird nach der Initialisierung automatisch aufgerufen und erlaubt es Ihnen, Daten zu validieren, abgeleitete Attribute zu berechnen oder andere Setup-Aufgaben auszuführen.

Grundlegende Verwendung von __post_init__

Die Methode __post_init__ wird aufgerufen, nachdem alle Attribute durch das generierte __init__ gesetzt wurden:

python
from dataclasses import dataclass
 
@dataclass
class Rectangle:
    width: float
    height: float
    area: float = 0.0  # Wird in __post_init__ berechnet
    
    def __post_init__(self):
        """Fläche nach der Initialisierung berechnen."""
        self.area = self.width * self.height
 
rect = Rectangle(5.0, 3.0)
print(rect)  # Output: Rectangle(width=5.0, height=3.0, area=15.0)
print(f"Area: {rect.area}")  # Output: Area: 15.0

Die Methode __post_init__ hat Zugriff auf alle Instanzattribute, die während der Initialisierung gesetzt wurden. Das ist nützlich, um abgeleitete Werte zu berechnen, die von mehreren Attributen abhängen.

Daten in post_init validieren

Ein häufiger Anwendungsfall für __post_init__ ist die Validierung, dass die bereitgestellten Daten bestimmte Anforderungen erfüllen:

python
from dataclasses import dataclass
 
@dataclass
class BankAccount:
    account_number: str
    balance: float
    
    def __post_init__(self):
        """Kontodaten validieren."""
        if self.balance < 0:
            raise ValueError("Balance cannot be negative")
 
# Gültiges Konto
account1 = BankAccount("ACC001", 1000.0)
print(account1)  # Output: BankAccount(account_number='ACC001', balance=1000.0)
 
# Ungültiges Konto – negativer Kontostand
try:
    account2 = BankAccount("ACC002", -500.0)
except ValueError as e:
    print(f"Error: {e}")  # Output: Error: Balance cannot be negative

Diese Validierung stellt sicher, dass Instanzen immer in einem gültigen Zustand sind. Wenn die Daten die Anforderungen nicht erfüllen, wird die Instanz nie erstellt, wodurch verhindert wird, dass ungültige Objekte in Ihrem Programm existieren.

post_init mit field(init=False) verwenden

Manchmal möchten Sie ein Attribut, das in __post_init__ berechnet wird, das aber kein Parameter in __init__ sein soll. Verwenden Sie dafür field(init=False):

python
from dataclasses import dataclass, field
import math
 
@dataclass
class Circle:
    radius: float
    area: float = field(init=False)  # Kein Parameter in __init__
    circumference: float = field(init=False)
    
    def __post_init__(self):
        """Fläche und Umfang aus radius berechnen."""
        self.area = math.pi * self.radius ** 2
        self.circumference = 2 * math.pi * self.radius
 
# Während der Initialisierung ist nur radius erforderlich
circle = Circle(5.0)
print(circle)  # Output: Circle(radius=5.0, area=78.53981633974483, circumference=31.41592653589793)
print(f"Area: {circle.area:.2f}")  # Output: Area: 78.54
print(f"Circumference: {circle.circumference:.2f}")  # Output: Circumference: 31.42

Dieses Muster ist nützlich, wenn Sie Attribute haben, die immer aus anderen Attributen berechnet werden und niemals direkt während der Initialisierung gesetzt werden sollten.


Data Classes stellen ein modernes Python-Feature dar, das Boilerplate reduziert und gleichzeitig die volle Leistungsfähigkeit von Klassen beibehält. Sie sind besonders wertvoll, um sauberen, gut lesbaren Code zu erstellen, wenn Sie mit strukturierten Daten arbeiten. Während Sie weiter Python lernen, werden Sie feststellen, dass Data Classes für viele datenorientierte Programmieraufgaben zu einer natürlichen Wahl werden und die normalen Klassen ergänzen, die Sie in den Kapiteln 30-32 gelernt haben.


© 2025. Primesoft Co., Ltd.
support@primesoft.ai