Dies ist ein maschinell übersetzter Text, der Fehler enthalten kann!
In Python (gilt auch für die meisten anderen Sprachen) ist es möglich, eigene Objekte mit eigenen Werten, Regeln und Funktionen zu erstellen. Dies wird als Klassen bezeichnet (auf Englisch „Classes“). Wir verwenden Classes, um zusammenhängende Daten und Funktionen in einer Einheit zu bündeln, z. B. eine Ordre-Klasse, die Daten wie ordre_id, kunde_navn, produkter sowie Funktionen wie legg_til_produkt(), beregn_total() usw. enthält.
Wörterbuch (JSON) vs Klassen (Objekte)
JSON (JavaScript Object Notation) ist ein Format zum Speichern und Übertragen von Daten unabhängig vom Programmiersprachenumfeld, während eine Klasse eine Struktur in einer bestimmten Programmiersprache darstellt.
Wenn wir Daten über das Netzwerk senden oder sie in einer Datei speichern möchten, verwenden wir häufig JSON (oder Tabellen in Datenbanken).
Wenn wir mit strukturierten Daten im Code arbeiten, verwenden wir Klassen.
In diesem Modul werden wir uns ansehen, wie Klassen zur Validierung von Daten verwendet werden können.
Video: Wie Klassen Daten und Regeln sammeln
Das Video erklärt, was eine Klasse ist und wie Klassen verwendet werden können, um Daten zu strukturieren und zu validieren.
Am einfachsten ist es, @dataclass aus der Bibliothek dataclasses zu verwenden. Dadurch müssen wir nicht viel Boilerplate-Code schreiben, um eine Klasse zu erstellen. (Wie z. B. die eingebauten Funktionen __init__ und __repr__ (Repräsentation)).
Beispiel für eine Class ohne Verwendung des Dataclass-Dekorators:
class Car:
def __init__(self, make: str, model: str, year: int):
self.make = make
self.model = model
self.year = year
def __repr__(self):
return f"{self.year} {self.make} {self.model}"
my_car = Car("Toyota", "Corolla", 2020)
print(my_car) # Ausgabe: 2020 Toyota Corolla
Beispiel mit Dataclass-Dekorator, das dasselbe wie oben erreicht (aber mit weniger Code):
from dataclasses import dataclass
@dataclass
class Car:
make: str
model: str
year: int
my_car = Car("Toyota", "Corolla", 2020)
print(my_car) # Ausgabe: Car(make='Toyota', model='Corolla', year=2020)
Aufgabe 1 - Eine Klasse erstellen
Erstellen Sie eine Klasse namens Person. Diese Klasse soll die folgenden Eigenschaften (Attributes) haben:
name: Der Name der Personeye_color: Die Augenfarbe der Personphone_number: Die Telefonnummer der Personemail: Die E-Mail-Adresse der Person
Instantiieren (verwenden) Sie ein Objekt der Klasse Person mit gültigen Werten für alle Eigenschaften, wie im Beispiel unten gezeigt.
@dataclass
class Person:
... # Dein Code hier
bob_kaare = Person(name="Bob Kåre",
eye_color="blue",
phone_number="12345678",
email="bob_kaare@example.com")
print(bob_kaare)
Lösung: Eine Dataclass für Person
Hier ist eine mögliche Lösung:
from dataclasses import dataclass
@dataclass
class Person:
name: str
eye_color: str
phone_number: str
email: str
Aufgabe 2 - Validierung in der Klasse
In dem Beispiel oben haben wir keine Validierung hinzugefügt. Das bedeutet, dass wir eine Person mit ungültigen Werten erstellen können, wie zum Beispiel:
@dataclass
class Person:
... # Ihr Code hier
invalid_person = Person(name="",
eye_color="yes",
phone_number="12345",
email="not-an-email")
print(invalid_person)
# Ausgabe: Person(name='', eye_color='yes', phone_number='12345', email='not-an-email')
Dies ist (potenziell) problematisch und kann in Zukunft schnell zu technischem Schulden führen. Glücklicherweise gibt es einfache Methoden, um Validierung in Klassen hinzuzufügen.
Wir beginnen damit, die E-Mail-Validierung zu betrachten. In Python sind eingebaute Bibliotheken vorhanden, die uns dabei helfen können, aber da wir dies zum Lernen tun, werden wir eine eigene simple Validierung erstellen, indem wir eine neue Klasse für „E-Mail“ anlegen und eine __post_init__-Funktion untersuchen (nur für Dataclasses).
Hinweis
Wir können dies auch in der eigentlichen Person-Klasse tun, aber es ist oft besser, eigene Klassen für wiederverwendbare Dinge zu erstellen.
Beispiel einer _postinit Funktion
from dataclasses import dataclass
@dataclass
class Email:
address: str
def __post_init__(self):
print(f"Validating email: {self.address}")
# Dein Code hier
Für eine einfache E-Mail-Validierung können wir beispielsweise prüfen, ob die E-Mail sowohl das @- als auch das .-Zeichen enthält. Gegebenenfalls können Sie prüfen, ob die E-Mail mit einem Regex-Muster übereinstimmt. (Fortgeschrittener, aber suchen Sie gerne im Internet!)
Lösung: Code für einfache E-Mail-Validierung
Hier ist eine mögliche Lösung; wir verwenden Exceptions, um bei ungültiger E-Mail „abzustürzen“. Dies stoppt das Programm sofort und gibt eine Fehlermeldung aus.
from dataclasses import dataclass
@dataclass
class Email:
address: str
def __post_init__(self):
if "@" not in self.address:
raise ValueError(f"Fehlt @ in der E-Mail-Adresse: {self.address}")
if "." not in self.address.split("@")[1]:
raise ValueError(f"Fehlt . im Domainnamen der E-Mail-Adresse: {self.address}")
if " " in self.address:
raise ValueError(f"Die E-Mail-Adresse darf keine Leerzeichen enthalten: {self.address}")
# Teste den Code
test = Email("hei@example.com") # Gültig
try:
test = Email("heiexample.com")
except ValueError as e:
print(e) # Ungültig, fehlt @
Aufgabe 3 – Validierung von Telefonnummern
Erstelle eine entsprechende Klasse wie bei E-Mails, diesmal jedoch für Telefonnummern
Herausforderung mit der Telefonvalidierung!
Können Sie die Validierung für Telefonnummern so reparieren, dass sie sowohl Buchstaben (str) als auch Zahlen (int) akzeptiert? Z.B. 12345678 und "12345678" sollen beide gültig sein.
Versuchen Sie auch, Ländercodes als Attribut (Unterwert der Klasse) hinzuzufügen. Z.B. 47 für Norwegen, 46 für Schweden
Aufgabe 4 – Verwendung der Validierung in der Person-Klasse
Da wir die Validierung für E-Mail-Adressen und Telefonnummern erstellt haben, können wir diese nun in der Person-Klasse verwenden.
@dataclass
class Person:
name: str
eye_color: str
phone_number: PhoneNumber # Verwenden Sie die PhoneNumber-Klasse
email: Email # Verwenden Sie die Email-Klasse
Neue Herausforderung entsteht!
Nun, da wir die Person-Klasse so geändert haben, dass sie die Klassen PhoneNumber und Email verwendet, müssen wir auch ändern, wie wir eine Person instanziieren (erstellen). Wir müssen jetzt zuerst ein PhoneNumber- und ein Email-Objekt erstellen, bevor wir eine Person erstellen können.
bob_kaare = Person(name="Bob Kåre",
eye_color="blue",
phone_number=PhoneNumber("12345678"), # Beachte die Änderung hier
email=Email("bob_kaare@example.com")) # Beachte die Änderung hier
print(bob_kaare)
# Hinweis: Eine Änderung muss auch in der Art, wie wir die Werte abrufen, vorgenommen werden
print(bob_kaare.email.address)
print(bob_kaare.phone_number.number) # .country_code(?)
Aufgabe 5 – Eigenschaften in Klassen (Optional)
Wenn wir Objekte verwenden, um Werte wie E-Mail-Adressen und Telefonnummern darzustellen, müssen wir den Untermen-Schlüssel (z. B. address für die E-Mail und number für die Telefonnummer) jedes Mal angeben, wenn wir den Wert abrufen möchten. Dies kann im Laufe der Zeit etwas umständlich werden. Glücklicherweise gibt es eine Lösung dafür: Durch Verwendung des @property-Decorators in einer Klasse können wir den Wert direkt aus dem Objekt abrufen, ohne den Untermen-Schlüssel angeben zu müssen.
Dies bringt jedoch noch eine weitere Herausforderung mit sich: Wir benötigen die Funktion __init__ in der Klasse Person. Der Grund hierfür ist, dass wir denselben Namen nicht sowohl für eine Eigenschaft als auch für ein Attribut in einem Dataclass verwenden können.
from dataclasses import dataclass
@dataclass
class EksempelVerdi:
attributt: str
@dataclass
class Person:
name: str
_verdi: EksempelVerdi # Interne Variable (beginnt mit _ um anzuzeigen, dass sie "privat" ist)
def __init__(self, name: str, verdi: EksempelVerdi):
self.name = name
self._verdi = verdi
@property
def verdi(self):
return self._verdi.attributt # Ruft den Unterwert direkt ab
# Test Code
person = Person(name="Alice", verdi=EksempelVerdi("Noe tekst"))
print(person.verdi) # Ausgabe: Noe tekst
Hinweis
Eigenschaften sind einzigartig, da sie keine Parameter benötigen und nicht in Klammern gesetzt werden müssen, um ausgeführt zu werden. Im Beispiel rufen wir person.wert ohne Klammern ab (nicht person.wert(), obwohl es technisch gesehen eine Funktion ist).
Alternatives Beispiel, das sowohl str als auch EksempelWert akzeptiert
@dataclass
class Person:
name: str
_wert: str
def __init__(self, name: str, wert: str | EksempelWert):
self.name = name
if isinstance(wert, EksempelWert):
self._wert = wert
elif isinstance(wert, str):
self._wert = EksempelWert(wert)
else:
raise TypeError("wert muss vom Typ str oder EksempelWert sein")
@property
def wert(self) -> str:
return self._wert.atribut # Holt den Untervalue direkt ab
# Code testen
person = Person(name="Alice", wert="Etwas Text")
print(person.wert) # Ausgabe: Etwas Text
Aufgabe 6 – Eigenschaften mit Logik (Optional)
Aktualisiere Person, indem du ein neues Attribut namens birthday (Geburtstag) hinzufügst. Dieses muss vom Typ datetime.date sein (aus der Bibliothek datetime).
Erstelle anschließend die folgenden Eigenschaften (Properties):
- Erzeuge eine Eigenschaft
age, die das Alter der Person auf Basis vonbirthdayund dem heutigen Datum berechnet. - Erzeuge eine Eigenschaft
is_adult, dieTruezurückgibt, wenn die Person 18 Jahre oder älter ist, andernfallsFalse.
Lösung: Alter und Erwachsener als Properties
Hier ist eine mögliche Lösung:
from dataclasses import dataclass
from datetime import date
@dataclass
class Person:
name: str
birthday: date
@property
def age(self) -> int:
"""Berechnet das Alter basierend auf dem Geburtsdatum und dem heutigen Datum"""
today = date.today()
age = today.year - self.birthday.year
# Reduziere um 1, wenn die Person dieses Jahr noch keinen Geburtstag hatte
if (today.month, today.day) < (self.birthday.month, self.birthday.day):
age -= 1
return age
@property
def is_adult(self) -> bool:
"""Gibt True zurück, wenn die Person 18 Jahre oder älter ist"""
return self.age >= 18
# Teste den Code
person = Person(name="Alice", birthday=date(2005, 5, 15))
print(person.age) # Z.B. 18, wenn das heutige Datum nach dem 15. Mai 2023 liegt
print(person.is_adult) # True
Eine weitere Herausforderung!
Schaffen Sie es, die Instanziierung von Person so zu gestalten, dass sie sowohl datetime.date als auch einen Text im Format "DD-MM-YYYY" für birthday akzeptiert? (Hinweis: Verwenden Sie datetime.strptime, um den String in ein datetime.date zu konvertieren)
