Hopp til innhold

Datenvalidierung und Klassen

Skip to content

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

British python devs be like thats a constructor, __init__?

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)

Easy Aufgabe 1 - Eine Klasse erstellen

Erstellen Sie eine Klasse namens Person. Diese Klasse soll die folgenden Eigenschaften (Attributes) haben:

  • name: Der Name der Person
  • eye_color: Die Augenfarbe der Person
  • phone_number: Die Telefonnummer der Person
  • email: 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

Medium 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 @

Medium 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

Medium 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(?)

Hard 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

Hard 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 von birthday und dem heutigen Datum berechnet.
  • Erzeuge eine Eigenschaft is_adult, die True zurückgibt, wenn die Person 18 Jahre oder älter ist, andernfalls False.
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)