Dataclass in Python

Fortgeschrittene Techniken mit Python dataclass

Licht am Ende des Tunnels

In diesem Kapitel unseres Python-Tutorials tauchen wir in die mächtige Welt der Python-Datenklassen ein. Datenklassen sind ein wesentliches Feature, was in Python 3.7 eingeführt wurde, um die Erstellung und Verwaltung von Klassen zu vereinfachen, die hauptsächlich zum Speichern von Daten verwendet werden. Zu Beginn des Kapitels werden die Unterschiede zwischen traditionellen Klassenstrukturen zur Darstellung von Daten aufgezeigt und Dataclasses als attraktive Alternative vorgestellt. Wir werden die Syntax und die Funktionalitäten von Datenklassen untersuchen und zeigen, wie die Lesbarkeit des Codes durch sie verbessert werden kann.

Erste Beispiele

In unserem ersten Beispiel bleiben wir unserem geliebten Roboter Marvin treu. Wir beginnen also mit einer "traditionellen" Python-Klasse Robot_traditional, die einen Roboter darstellt:

In [3]:
class Robot_traditional:
    
    def __init__(self, model, serial_number, manufacturer):
        self.model = model
        self.serial_number = serial_number
        self.manufacturer = manufacturer

Der Standardcode innerhalb der __init__-Methode folgt einem ähnlichen Musterin fast allen Klassendefinition. So auch indiesem Beispiel, wobei die einzige Variation die Namen der den Attributen zugewiesenen Bezeichnungen sind. Dies wird besonders mühsam, wenn die Anzahl der Attribute zunimmt.

Wenn wir uns das gleiche Beispiel in der Notation mittels dataclass anschauen, sehen wir, dass der Code erheblich schlanker wird. Bevor wir jedoch dataclass als Dekorateur für unsere Klasse verwenden können, müssen wir es aus dem Modul dataclasses importieren.

In [4]:
from dataclasses import dataclass

@dataclass
class Robot:
    model: str
    serial_number: str
    manufacturer: str

Hier automatisiert der Datenklassen-Dekorator die Generierung spezieller Methoden wie __init__, wodurch der Bedarf an Boilerplate-Code reduziert wird. Die Klassendefinition ist prägnant, was sie übersichtlicher und wartungsfreundlicher macht, insbesondere wenn die Anzahl der Attribute steigt.

Dieses Beispiel zeigt, wie die Verwendung von Datenklassen für eine Klasse, die hauptsächlich zum Speichern von Daten verwendet wird, wie z. B. eine Roboterdarstellung, eine schlankere und lesbarere Alternative zu traditionellen Klassenstrukturen bietet.

Die Initialisierung von Robotern beider Klassen ist identisch;

In [6]:
x = Robot_traditional("NanoGuardian XR-2000", "234-76", "Cyber Robotics Co.")
y = Robot("MachinaMaster MM-42", "986-42", "Quantum Automations Inc.")

Doch es gibt noch weitere Unterschiede in diesen Klassen. Der Klassendekorator dataclass hat nicht nur die spezielle Methode __init__ erstellt, sondern auch __repr__, __eq__, __ne__ und __hash__. Methoden, die Sie ansonsten manuell zum Aufruf Robot_traditional hinzufügen müssten.

Werfen wir einen Blick auf __repr__ und vergleichen wir es mit der traditionellen Klassendefinition:

In [8]:
class Robot_traditional:
    
    def __init__(self, model, serial_number, manufacturer):
        self.model = model
        self.serial_number = serial_number
        self.manufacturer = manufacturer

    def __repr__(self):
        return f"Robot_traditional(model='{self.model}', serial_number='{self.serial_number}', manufacturer='{self.manufacturer}')"

x = Robot_traditional("NanoGuardian XR-2000", "234-76", "Cyber Robotics Co.")

print(repr(x))
Robot_traditional(model='NanoGuardian XR-2000', serial_number='234-76', manufacturer='Cyber Robotics Co.')

Unveränderliche Klassen

In unserem Kapitel zu Unveränderliche Klassen in Python (Immutable Klassen unseres Tutorials diskutieren wir die Gründe für die Notwendigkeit von Unveränderlichkeit und erkunden verschiedene Methoden zu ihrer Erstellung.

Mit Dataclasses ist es sehr einfach. Alles, was man tun muss, ist, den Dekorator mit frozen=True aufzurufen:

In [9]:
from dataclasses import dataclass

@dataclass(frozen=True)
class ImmutableRobot:
    name: str
    brandname: str

Mit dieser Klasse können wir zwei Roboter auf Gleichheit prüfen. Sie sind gleich, wenn alle Attribute gleich sind. Denken Sie daran, dass __eq__ automatisch erstellt wurde.

In [10]:
x1 = ImmutableRobot("Marvin", "NanoGuardian XR-2000")
x2 = ImmutableRobot("Marvin", "NanoGuardian XR-2000")

print(x1 == x2)
True

Schauen wir uns die Hahs-Werte an:

In [12]:
print(x1.__hash__(), x2.__hash__())
-6791219099913027866 -6791219099913027866

Zunächst können wir sehen, dass __hash__ automatisch erstellt und korrekt implementiert wurde. Dadurch wird sichergestellt, dass Roboter, die gleich sind, identische Hash-Werte erzeugen.

Nun wollen wir eine ähnliche Klasse implementieren, ohne eine Datenklasse zu verwenden. In unserer zuvor definierten Klasse Robot_traditional sind die Instanzen veränderbar, da wir die Möglichkeit haben, die Attribute zu ändern. Das folgende Beispiel ist eine unveränderliche Version. Wir können sofort sehen, dass es eine Menge mehr Kodierungsaufwand gibt. Wir müssen __init__', die Getter-Eigenschaften,equndhash` implementieren:

In [13]:
class ImmutableRobot_traditional:
    
    def __init__(self, name: str, brandname: str):
        self._name = name
        self._brandname = brandname

    @property
    def name(self) -> str:
        return self._name

    @property
    def brandname(self) -> str:
        return self._brandname

    def __eq__(self, other):
        if not isinstance(other, ImmutableRobot_traditional):
            return False
        return self.name == other.name and self.brandname == other.brandname

    def __hash__(self):
        return hash((self.name, self.brandname))

x1 = ImmutableRobot_traditional("Marvin", "NanoGuardian XR-2000")
x2 = ImmutableRobot_traditional("Marvin", "NanoGuardian XR-2000")

print(x1 == x2)
print(x1.__hash__(), x2.__hash__())
True
-6791219099913027866 -6791219099913027866
In [14]:
print(x1.__hash__(), x2.__hash__())
-6791219099913027866 -6791219099913027866

Eine unveränderliche Klasse mit einer `hash-Methode zu haben, bedeutet, dass wir unsere Klasse in Mengen und Wörterbüchern verwenden können. Wir veranschaulichen dies im folgenden Beispiel:

In [1]:
from dataclasses import dataclass

@dataclass(frozen=True)
class ImmutableRobot:
    name: str
    brandname: str

robot1 = ImmutableRobot("Marvin", "NanoGuardian XR-2000")
robot2 = ImmutableRobot("R2D2", "QuantumTech Sentinel-7")
robot3 = ImmutableRobot("Marva", "MachinaMaster MM-42")

# we create a set of Robots:
robots = {robot1, robot2, robot3}

print("The robots in the set robots:")
for robo in robots:
    print(robo)

# now a dictionary with robots as keys:
activity = {robot1: 'activated', robot2: 'activated', robot3: 'deactivated'}

print("\nAll the activated robots:")
for robo, mode in activity.items():
    if mode == 'activated':
        print(f"{robo} is activated")
The robots in the set robots:
ImmutableRobot(name='Marvin', brandname='NanoGuardian XR-2000')
ImmutableRobot(name='R2D2', brandname='QuantumTech Sentinel-7')
ImmutableRobot(name='Marva', brandname='MachinaMaster MM-42')

All the activated robots:
ImmutableRobot(name='Marvin', brandname='NanoGuardian XR-2000') is activated
ImmutableRobot(name='R2D2', brandname='QuantumTech Sentinel-7') is activated

Zusammenfassung

Die Verwendung einer Datenklasse bietet mehrere Vorteile gegenüber traditionellen Klassendefinitionen in Python:

  • Automatische Generierung von speziellen Methoden: Mit Datenklassen müssen Sie spezielle Methoden wie __init__, __repr__, __eq__, __hash__ usw. nicht manuell schreiben. Der dataclass-Dekorator generiert diese Methoden automatisch basierend auf den Klassenattributen, die Sie definieren.

  • Prägnante Syntax: dataclass verwendet eine prägnante Syntax zur Definition von Klassen, wodurch Boilerplate-Code reduziert wird. Sie müssen nur die Attribute der Klasse angeben, und der Dekorator kümmert sich um den Rest.

  • Integrierte Vergleichsmethoden: dataclass bietet automatisch Implementierungen für Vergleichsmethoden wie __eq__, __ne__, __lt__, __le__, __gt__ und __ge__, basierend auf den Attributen der Klasse.

  • Unveränderliche Instanzen: Durch die Angabe von frozen=True im dataclass-Dekorator können Instanzen der Klasse unveränderlich gemacht werden, was dazu beitragen kann, versehentliche Änderungen an Daten zu verhindern.

  • Integration mit Typ-Hinweisen: dataclass integriert sich nahtlos in das Typ-Hinweis-System von Python, sodass Sie die Typen von Attributen für eine bessere Code-Lesbarkeit und statische Analyse angeben können.

  • Standardwerte und Standardwerte-Fabrik: Sie können Standardwerte für Attribute direkt in der Klassendefinition oder unter Verwendung von Fabrikfunktionen angeben, was die Notwendigkeit für Boilerplate-Code in der __init__-Methode reduziert.

  • Unterstützung für Vererbung: dataclass unterstützt Vererbung, sodass Sie Unterklassen mit zusätzlichen Attributen oder Methoden erstellen können, während Sie das Verhalten der Elternklasse erben.

  • Anpassung: Obwohl dataclass die automatische Generierung spezieller Methoden bietet, können Sie diese Methoden bei Bedarf weiterhin anpassen oder überschreiben, was Ihnen Flexibilität bei der Definition des Klassenverhaltens gibt.

Insgesamt vereinfacht dataclass den Prozess der Klassenerstellung in Python und macht den Code prägnanter, lesbarer und wartbarer, insbesondere für Klassen, die hauptsächlich als Container für Daten dienen.

This Markdown text provides the translated version of the original text into German.

Exercises

Exercise 1: Buchinformationen

Erstellen Sie eine Book-Klasse mithilfe von dataclass, um Informationen über Bücher darzustellen. Jedes Buch sollte die folgenden Attribute haben:

  • Titel
  • Autor
  • ISBN (Internationale Standardbuchnummer)
  • Veröffentlichungsjahr
  • Genre

Schreiben Sie ein Programm, das Folgendes tut:

  1. Definieren Sie die Book-Klasse mit dataclass.
  2. Erstellen Sie Instanzen mehrerer Bücher.
  3. Geben Sie die Details jedes Buches aus, einschließlich Titel, Autor, ISBN, Veröffentlichungsjahr und Genre.

Sie können diese Übung verwenden, um das Definieren von dataclass, das Erstellen von Instanzen und den Zugriff auf Attribute von dataclass-Objekten zu üben. Darüber hinaus können Sie erkunden, wie Sie der Book-Klasse Methoden oder Anpassungen hinzufügen können, z. B. eine Methode zur Berechnung des Alters des Buches basierend auf dem Veröffentlichungsjahr oder die Validierung von ISBN-Nummern hinzufügen.

Solution

Solution 1:

In [2]:
from dataclasses import dataclass

@dataclass
class Book:
    title: str
    author: str
    isbn: str
    publication_year: int
    genre: str

# Create instances of several books
buch1 = Book("Die Verwandlung", "Franz Kafka", "9783150091563", 1915, "Roman")
buch2 = Book("Faust. Der Tragödie erster Teil", "Johann Wolfgang von Goethe", "9783150000008", 1808, "Drama")
buch3 = Book("Der Steppenwolf", "Hermann Hesse", "9783518366868", 1927, "Roman")

# Print out the details of each book
print("Buch 1:")
print("Titel:", buch1.title)
print("Autor:", buch1.author)
print("ISBN:", buch1.isbn)
print("Veröffentlichungsjahr:", buch1.publication_year)
print("Genre:", buch1.genre)

print("\nBuch 2:")
print("Titel:", buch2.title)
print("Autor:", buch2.author)
print("ISBN:", buch2.isbn)
print("Veröffentlichungsjahr:", buch2.publication_year)
print("Genre:", buch2.genre)

print("\nBuch 3:")
print("Titel:", buch3.title)
print("Autor:", buch3.author)
print("ISBN:", buch3.isbn)
print("Veröffentlichungsjahr:", buch3.publication_year)
print("Genre:", buch3.genre)
Buch 1:
Titel: Die Verwandlung
Autor: Franz Kafka
ISBN: 9783150091563
Veröffentlichungsjahr: 1915
Genre: Roman

Buch 2:
Titel: Faust. Der Tragödie erster Teil
Autor: Johann Wolfgang von Goethe
ISBN: 9783150000008
Veröffentlichungsjahr: 1808
Genre: Drama

Buch 3:
Titel: Der Steppenwolf
Autor: Hermann Hesse
ISBN: 9783518366868
Veröffentlichungsjahr: 1927
Genre: Roman