Dies ist ein maschinell übersetzter Text, der Fehler enthalten kann!
In Python (dies gilt auch für die meisten anderen Sprachen) ist es möglich, eigene Objekte mit eigenen Werten, Regeln und Funktionen zu erstellen. Dies nennt man Klassen (classes auf Englisch). Wir verwenden Klassen, um verwandte Daten und Funktionen in einer Einheit zusammenzufassen, z. B. eine Bestellung Klasse mit Daten wie bestellungs_id, kunden_name, produkte und Funktionen wie produkt_hinzufügen(), gesamtbetrag_berechnen(), usw.
Dictionary (JSON) vs Klasser (Objekter)
JSON (JavaScript Object Notation) ist ein Format zum Speichern und Übertragen von Daten unabhängig von der Programmiersprache, während eine Klasse eine Struktur in einer bestimmten Programmiersprache ist.
Wenn wir Daten über das Netzwerk senden oder in einer Datei speichern möchten, verwenden wir oft JSON (oder Tabellen in Datenbanken).
Wenn wir mit strukturierten Daten im Code arbeiten möchten, verwenden wir Klassen.
In diesem Modul werden wir uns ansehen, wie wir Klassen zur Datenvalidierung verwenden können.
Am einfachsten ist es, @dataclass aus der dataclasses-Bibliothek zu verwenden. Dies erspart uns das Schreiben viel Boilerplate-Code zum Erstellen einer Klasse. (Wie z. B. die eingebauten __init__ und __repr__ (Repräsentation) Funktionen).
Beispiel für eine Klasse ohne die 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 dem Dataclass-Dekorator, der das Gleiche 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 - Erstelle eine Klasse
Erstelle eine Klasse namens Person. Diese Klasse soll die folgenden Eigenschaften (Attribute) haben:
name: Der Name der Personeye_color: Die Augenfarbe der Personphone_number: Die Telefonnummer der Personemail: Die E-Mail-Adresse der Person
Instanziiere (verwende) ein Objekt der Klasse Person mit gültigen Werten für alle Eigenschaften, wie im folgenden Beispiel.
@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 zu technischer Schuld führen. Glücklicherweise gibt es einfache Möglichkeiten, Klassen Validierungen hinzuzufügen.
Wir werden zunächst die E-Mail-Validierung betrachten. Es gibt integrierte Bibliotheken in Python, die uns dabei helfen können, aber da wir dies zum Lernen tun, werden wir unsere eigene einfache Validierung erstellen, indem wir eine neue Klasse für “Email” erstellen und eine __post_init__-Funktion untersuchen (nur für Dataclasses).
Merk
Wir können dies auch in der eigentlichen Person-Klasse tun, aber es ist oft besser, eigene Klassen für Dinge zu erstellen, die wiederverwendet werden können.
Beispiel einer post_init 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 “abzustürzen”, falls die E-Mail ungültig ist, dies wird das Programm sofort stoppen und eine Fehlermeldung ausgeben.
from dataclasses import dataclass
@dataclass
class Email:
address: str
def __post_init__(self):
if "@" not in self.address:
raise ValueError(f"Mangelndes @ in der E-Mail-Adresse: {self.address}")
if "." not in self.address.split("@")[1]:
raise ValueError(f"Mangelnder . in der Domäne der E-Mail-Adresse: {self.address}")
if " " in self.address:
raise ValueError(f"Die E-Mail-Adresse darf keine Leerzeichen enthalten: {self.address}")
# Testen Sie den Code
test = Email("hei@example.com") # Gültig
try:
test = Email("heiexample.com")
except ValueError as e:
print(e) # Ungültig, mangelt @
Aufgabe 3 - Telefonnummer Validierung
Erstellen Sie analog eine Klasse, wie Sie es für E-Mail-Adressen getan haben, aber jetzt 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 – Verwenden Sie Validierung in der Person-Klasse
Nun, da wir die Validierung für E-Mail und Telefonnummer erstellt haben, können wir diese 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)
# Anmerkung: Eine Änderung muss auch an der Art und Weise vorgenommen werden, wie wir die Werte extrahieren
print(bob_kaare.email.address)
print(bob_kaare.phone_number.number) # .country_code(?)
Aufgabe 5 - Properties in Klassen (Optional)
Wenn wir Objekte verwenden, um Werte wie E-Mail und Telefonnummern darzustellen, müssen wir den Unterwert (z. B. address für E-Mail und number für Telefonnummer) jedes Mal angeben, wenn wir den Wert abrufen möchten. Dies kann auf Dauer etwas umständlich werden. Glücklicherweise gibt es eine Lösung dafür, indem wir den @property-Dekorator in einer Klasse verwenden, der es uns ermöglicht, den Wert direkt aus dem Objekt abzurufen, ohne den Unterwert angeben zu müssen.
Dies birgt jedoch eine weitere Herausforderung, und zwar benötigen wir die __init__-Funktion in der Person-Klasse. Dies liegt daran, dass wir denselben Namen nicht sowohl für eine Property als auch für ein Attribut in einer 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
Merk
Eigenschaften sind einzigartig, da sie keine Parameter benötigen und keine Klammern für die Ausführung benötigen. In dem Beispiel holen wir person.verdi ohne Klammern (nicht person.verdi()), auch wenn es sich technisch gesehen um eine Funktion handelt.
Alternativ Beispiel, das sowohl str als auch EksempelVerdi akzeptiert
@dataclass
class Person:
name: str
_verdi: str
def __init__(self, name: str, verdi: str | EksempelVerdi):
self.name = name
if isinstance(verdi, EksempelVerdi):
self._verdi = verdi
elif isinstance(verdi, str):
self._verdi = EksempelVerdi(verdi)
else:
raise TypeError("verdi muss vom Typ str oder EksempelVerdi sein")
@property
def verdi(self) -> str:
return self._verdi.attributt # Ruft den Unterwert direkt ab
# Teste den Code
person = Person(name="Alice", verdi="Noch ein Text")
print(person.verdi) # Ausgabe: Noch ein Text
Aufgabe 6 - Properties mit Logik (Optional)
Aktualisiere Person, indem du ein neues Attribut namens birthday (Geburtstag) hinzufügst. Dieses soll vom Typ datetime.date (aus der datetime Bibliothek) sein.
Erstelle anschließend folgende Properties:
- Erstelle eine Property
age, die das Alter der Person basierend aufbirthdayund dem heutigen Datum berechnet. - Erstelle eine Property
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)
