Python & AI Tutorials Logo
Python Programmierung

26. Defensive Programmiertechniken mit Exceptions und Validierung

Defensives Programmieren (defensive programming) bedeutet, Code zu schreiben, der Probleme antizipiert, bevor sie auftreten. Statt anzunehmen, dass alles perfekt funktioniert, validiert defensiver Code Eingaben, behandelt Fehler elegant und überprüft Annahmen. Dieser Ansatz erstellt Programme, die zuverlässiger sind, sich leichter debuggen lassen und weniger wahrscheinlich unerwartet abstürzen.

In früheren Kapiteln haben wir gelernt, wie man Exceptions behandelt, wenn sie auftreten. Jetzt lernen wir, wie man viele Fehler verhindert, bevor sie überhaupt passieren, und wie man Probleme frühzeitig abfängt, wenn sie doch auftreten.

26.1) Funktionsargumente validieren

Funktionen (functions) erhalten oft Daten aus anderen Teilen Ihres Programms oder von Benutzern. Wenn eine Funktion ungültige Daten erhält, kann sie falsche Ergebnisse liefern, mit einem verwirrenden Fehler abstürzen oder anderswo in Ihrem Programm Probleme verursachen. Argumentvalidierung (argument validation) bedeutet, zu prüfen, ob Funktionsargumente Ihre Anforderungen erfüllen, bevor Sie sie verwenden.

26.1.1) Warum Argumente validieren?

Betrachten Sie diese Funktion, die den Prozentanteil der Note eines Schülers berechnet:

python
def calculate_percentage(points_earned, total_points):
    return (points_earned / total_points) * 100
 
# Verwendung der Funktion
percentage = calculate_percentage(85, 100)
print(f"Grade: {percentage}%")  # Output: Grade: 85.0%

Das funktioniert mit gültigen Eingaben problemlos. Aber was passiert bei problematischen Daten?

python
# Problem 1: Division durch Null
percentage = calculate_percentage(85, 0)  # ZeroDivisionError!
 
# Problem 2: Negative Werte (ergibt keinen Sinn)
percentage = calculate_percentage(-10, 100)  # -10.0%
 
# Problem 3: Erreichte Punkte übersteigen Gesamtpunkte (unmöglich)
percentage = calculate_percentage(120, 100)  # 120.0%

Ohne Validierung stürzt die Funktion entweder ab oder erzeugt unsinnige Ergebnisse. Die Fehlermeldungen erklären nicht, was aus Sicht der Geschäftslogik falsch gelaufen ist – sie zeigen nur technische Fehler an.

26.1.2) Einfache Argumentvalidierung mit Bedingungen

Der einfachste Validierungsansatz verwendet if-Anweisungen, um Argumente zu prüfen und Exceptions auszulösen, wenn sie ungültig sind:

python
def calculate_percentage(points_earned, total_points):
    # total_points validieren
    if total_points <= 0:
        raise ValueError("total_points must be positive")
    
    # points_earned validieren
    if points_earned < 0:
        raise ValueError("points_earned cannot be negative")
    
    if points_earned > total_points:
        raise ValueError("points_earned cannot exceed total_points")
    
    # Alle Validierungen bestanden – Berechnung ist sicher
    return (points_earned / total_points) * 100
 
# Gültige Verwendung
percentage = calculate_percentage(85, 100)
print(f"Grade: {percentage}%")  # Output: Grade: 85.0%
 
# Ungültige Verwendung – klare Fehlermeldungen
try:
    percentage = calculate_percentage(85, 0)
except ValueError as e:
    print(f"Error: {e}")  # Output: Error: total_points must be positive
 
try:
    percentage = calculate_percentage(-10, 100)
except ValueError as e:
    print(f"Error: {e}")  # Output: Error: points_earned cannot be negative
 
try:
    percentage = calculate_percentage(120, 100)
except ValueError as e:
    print(f"Error: {e}")  # Output: Error: points_earned cannot exceed total_points

Jetzt erklärt die Fehlermeldung, wenn etwas schiefgeht, klar, worin das Problem besteht und wie Sie es beheben können.

26.1.3) Argumenttypen validieren

Manchmal müssen Sie sicherstellen, dass Argumente den richtigen Typ haben:

python
def calculate_discount(price, discount_percent):
    # Typen validieren
    if not isinstance(price, (int, float)):
        raise TypeError("price must be a number")
    
    if not isinstance(discount_percent, (int, float)):
        raise TypeError("discount_percent must be a number")
    
    # Werte validieren
    if price < 0:
        raise ValueError("price cannot be negative")
    
    if not (0 <= discount_percent <= 100):
        raise ValueError("discount_percent must be between 0 and 100")
    
    # Rabatt berechnen
    discount_amount = price * (discount_percent / 100)
    return price - discount_amount
 
# Gültige Verwendung
final_price = calculate_discount(50.00, 20)
print(f"Final price: ${final_price:.2f}")  # Output: Final price: $40.00
 
# Typfehler
try:
    final_price = calculate_discount("50", 20)
except TypeError as e:
    print(f"Error: {e}")  # Output: Error: price must be a number
 
# Wertefehler
try:
    final_price = calculate_discount(50.00, 150)
except ValueError as e:
    print(f"Error: {e}")  # Output: Error: discount_percent must be between 0 and 100

Die Funktion isinstance() prüft, ob ein Objekt eine Instanz eines angegebenen Typs oder mehrerer Typen ist. Wir übergeben ein Tupel (int, float), um entweder Integers oder Floats zu akzeptieren, da beide gültige numerische Typen für Preise sind.

Wann man Typen validieren sollte: Pythons Philosophie ist „duck typing“ – wenn sich ein Objekt so verhält, wie Sie es brauchen, verwenden Sie es. Typvalidierung ist am nützlichsten, wenn:

  • Sie eine Funktion schreiben, die von anderen verwendet wird
  • Typfehler später verwirrende Fehler verursachen würden
  • Die Funktion Teil einer öffentlichen API oder Bibliothek ist

26.1.4) Collection-Argumente validieren

Wenn Funktionen Listen, Wörterbücher oder andere Sammlungen (collections) akzeptieren, validieren Sie sowohl die Sammlung als auch deren Inhalte:

python
def calculate_average_grade(grades):
    # Die Collection selbst validieren
    if not isinstance(grades, list):
        raise TypeError("grades must be a list")
    
    if len(grades) == 0:
        raise ValueError("grades list cannot be empty")
    
    # Jede Note in der Collection validieren
    for i, grade in enumerate(grades):
        if not isinstance(grade, (int, float)):
            raise TypeError(f"grade at index {i} must be a number, got {type(grade).__name__}")
        
        if not (0 <= grade <= 100):
            raise ValueError(f"grade at index {i} must be between 0 and 100, got {grade}")
    
    # Alle Validierungen bestanden
    return sum(grades) / len(grades)
 
# Gültige Verwendung
grades = [85, 92, 78, 95]
average = calculate_average_grade(grades)
print(f"Average: {average:.1f}")  # Output: Average: 87.5
 
# Fehler bei leerer Liste
try:
    average = calculate_average_grade([])
except ValueError as e:
    print(f"Error: {e}")  # Output: Error: grades list cannot be empty
 
# Ungültiger Notentyp
try:
    average = calculate_average_grade([85, "92", 78])
except TypeError as e:
    print(f"Error: {e}")  # Output: Error: grade at index 1 must be a number, got str
 
# Ungültiger Notenwert
try:
    average = calculate_average_grade([85, 92, 150])
except ValueError as e:
    print(f"Error: {e}")  # Output: Error: grade at index 2 must be between 0 and 100, got 150

Beachten Sie, wie wir beim Validieren von Collection-Elementen den Index in Fehlermeldungen angeben. Das hilft dabei, genau zu identifizieren, welches Element problematisch ist, besonders bei großen Sammlungen.

Ungültiger Typ

Ungültiger Wert

Gültig

Funktion aufgerufen

Argumente
validieren

TypeError auslösen

ValueError auslösen

Funktionslogik ausführen

Ergebnis zurückgeben

Aufrufer behandelt Exception

26.2) Benutzereingaben auf Gültigkeit prüfen

Benutzereingaben sind grundsätzlich unzuverlässig – Benutzer machen Tippfehler, missverstehen Anweisungen oder geben Daten in unerwarteten Formaten ein. Die Validierung von Benutzereingaben verhindert, dass diese Fehler zu Programmabstürzen oder falschen Ergebnissen führen.

26.2.1) Grundlegendes Muster zur Eingabevalidierung

Das grundlegende Muster für Eingabevalidierung kombiniert input() mit Validierungsprüfungen:

python
# Benutzereingabe holen
age_str = input("Enter your age: ")
 
# Eingabe validieren
try:
    age = int(age_str)
    if age < 0:
        print("Error: Age cannot be negative")
    elif age > 150:
        print("Error: Age seems unrealistic")
    else:
        print(f"You are {age} years old")
except ValueError:
    print("Error: Please enter a valid number")

Dieses Muster hat drei Teile:

  1. Die Eingabe als String holen
  2. Versuchen, sie in den benötigten Typ umzuwandeln
  3. Prüfen, ob der umgewandelte Wert gültig ist

Schauen wir uns das mit verschiedenen Eingaben in Aktion an:

python
# Gültige Eingabe
# User enters: 25
# Output: You are 25 years old
 
# Ungültiger Typ
# User enters: twenty-five
# Output: Error: Please enter a valid number
 
# Ungültiger Wert (negativ)
# User enters: -5
# Output: Error: Age cannot be negative
 
# Ungültiger Wert (unrealistisch)
# User enters: 200
# Output: Error: Age seems unrealistic

26.2.2) Eingabebereiche und Formate validieren

Einige Eingaben müssen innerhalb bestimmter Bereiche liegen oder bestimmten Formaten entsprechen:

python
# Einen Monat validieren (1-12)
month_str = input("Enter month (1-12): ")
try:
    month = int(month_str)
    if not (1 <= month <= 12):
        print("Error: Month must be between 1 and 12")
    else:
        print(f"Month: {month}")
except ValueError:
    print("Error: Please enter a whole number")
 
# E-Mail-Format validieren (einfache Prüfung)
email = input("Enter email: ")
if '@' not in email or '.' not in email:
    print("Error: Email must contain @ and .")
else:
    print(f"Email: {email}")
 
# Ja/Nein-Eingabe validieren
response = input("Continue? (yes/no): ").lower().strip()
if response not in ['yes', 'no', 'y', 'n']:
    print("Error: Please answer yes or no")
else:
    if response in ['yes', 'y']:
        print("Continuing...")
    else:
        print("Stopping...")

Die E-Mail-Validierung hier ist absichtlich einfach – sie prüft nur auf eine grundlegende Struktur. Echte E-Mail-Validierung ist deutlich komplexer und verwendet typischerweise reguläre Ausdrücke (die wir in Kapitel 39 kennenlernen).

26.2.3) Hilfreiche Fehlermeldungen bereitstellen

Gute Fehlermeldungen sagen Benutzern genau, was schiefgelaufen ist und wie sie es beheben können:

python
# Schlechte Fehlermeldung
password = input("Enter password: ")
if len(password) < 8:
    print("Error: Invalid password")  # Nicht hilfreich!
 
# Bessere Fehlermeldung
password = input("Enter password: ")
if len(password) < 8:
    print("Error: Password must be at least 8 characters long")
    print(f"Your password is only {len(password)} characters")
 
# Noch besser – alle Anforderungen vorab erklären
print("Password requirements:")
print("- At least 8 characters")
print("- Must contain at least one number")
password = input("Enter password: ")
 
# Länge prüfen
if len(password) < 8:
    print(f"Error: Password too short ({len(password)} characters)")
    print("Password must be at least 8 characters")
# Auf Ziffer prüfen
elif not any(char.isdigit() for char in password):
    print("Error: Password must contain at least one number")
else:
    print("Password accepted")

Die Funktion any() gibt True zurück, wenn irgendein Element in einem Iterable wahr ist. Hier prüft char.isdigit(), ob jedes Zeichen eine Ziffer ist, und any() sagt uns, ob mindestens ein Zeichen den Test bestanden hat.

Umwandlung schlägt fehl

Umwandlung gelingt

Außerhalb des Bereichs

Ungültiges Format

Gültig

Benutzereingabe holen

Typumwandlung versuchen

ValueError:
Ungültiges Format

Wert-
Constraints prüfen

Value Error:
Klare Meldung

Formatfehler:
Klare Meldung

Eingabe verwenden

Fehler anzeigen,
Erwartetes Format erklären

26.3) input(), Schleifen und try/except für robuste Eingabebehandlung kombinieren

Einzelne Validierungsprüfungen sind nützlich, aber sie gehen nicht mit wiederholten Benutzerfehlern um. Wenn ein Benutzer ungültige Daten eingibt, sollte Ihr Programm ihm eine weitere Chance geben. Die Kombination von Schleifen mit Validierung erzeugt eine robuste Eingabebehandlung, die so lange nachfragt, bis sie gültige Daten erhält.

26.3.1) Das grundlegende Eingabe-Schleifenmuster

Das grundlegende Muster verwendet eine while-Schleife, die fortgesetzt wird, bis eine gültige Eingabe empfangen wurde:

python
# So lange fragen, bis wir ein gültiges Alter erhalten
while True:
    age_str = input("Enter your age: ")
    try:
        age = int(age_str)
        if age < 0:
            print("Error: Age cannot be negative. Please try again.")
        elif age > 150:
            print("Error: Age seems unrealistic. Please try again.")
        else:
            # Gültige Eingabe – Schleife verlassen
            break
    except ValueError:
        print("Error: Please enter a valid number.")
 
print(f"You are {age} years old")

Dieses Muster hat mehrere Schlüsselelemente:

  • while True: erzeugt eine Endlosschleife
  • Die Validierung passiert innerhalb der Schleife
  • break beendet die Schleife, wenn die Eingabe gültig ist
  • Fehlermeldungen ermutigen den Benutzer, es erneut zu versuchen

Schauen wir, wie das verschiedene Eingaben behandelt:

python
# Beispielinteraktion:
# Enter your age: twenty
# Error: Please enter a valid number.
# Enter your age: -5
# Error: Age cannot be negative. Please try again.
# Enter your age: 25
# You are 25 years old

26.3.2) Wiederverwendbare Eingabefunktionen erstellen

Wenn Sie dieselbe Art von validierter Eingabe an mehreren Stellen benötigen, erstellen Sie eine Funktion:

python
def get_positive_integer(prompt):
    """Keep asking until user enters a positive integer."""
    while True:
        try:
            value = int(input(prompt))
            if value <= 0:
                print("Error: Please enter a positive number.")
            else:
                return value
        except ValueError:
            print("Error: Please enter a valid whole number.")
 
def get_number_in_range(prompt, min_value, max_value):
    """Keep asking until user enters a number in the specified range."""
    while True:
        try:
            value = float(input(prompt))
            if value < min_value or value > max_value:
                print(f"Error: Please enter a number between {min_value} and {max_value}.")
            else:
                return value
        except ValueError:
            print("Error: Please enter a valid number.")
 
# Verwendung der Funktionen
quantity = get_positive_integer("Enter quantity: ")
print(f"Quantity: {quantity}")
 
grade = get_number_in_range("Enter grade (0-100): ", 0, 100)
print(f"Grade: {grade}")
 
temperature = get_number_in_range("Enter temperature (-50 to 50): ", -50, 50)
print(f"Temperature: {temperature}°C")

Diese Funktionen kapseln die Validierungslogik, wodurch Ihr Hauptcode sauberer und besser lesbar wird. Sie sorgen außerdem für konsistentes Validierungsverhalten in Ihrem gesamten Programm.

26.4) Assertions für Invariantenprüfungen zur Entwicklungszeit verwenden

Assertions sind eine besondere Art von Prüfung, die während der Entwicklung verwendet wird, um zu verifizieren, dass die Annahmen Ihres Codes korrekt sind. Anders als Validierung (die erwartete Fehler von Benutzern oder externen Daten behandelt) fangen Assertions Programmierfehler ab – Situationen, die niemals passieren sollten, wenn Ihr Code korrekt ist.

26.4.1) Was Assertions sind und wann man sie verwenden sollte

Eine Assertion ist eine Anweisung, die zu einem bestimmten Zeitpunkt in Ihrem Code immer wahr sein sollte. Ist sie falsch, stimmt grundsätzlich etwas mit Ihrer Programmlogik nicht:

python
def calculate_average(numbers):
    # Das sollte niemals passieren, wenn die Funktion korrekt aufgerufen wird
    assert len(numbers) > 0, "numbers list cannot be empty"
    
    return sum(numbers) / len(numbers)
 
# Korrekte Verwendung
grades = [85, 90, 78]
average = calculate_average(grades)
print(f"Average: {average:.1f}")  # Output: Average: 84.3
 
# Falsche Verwendung – löst Assertion aus
empty_list = []
average = calculate_average(empty_list)  # AssertionError: numbers list cannot be empty

Wenn eine Assertion fehlschlägt, löst Python einen AssertionError mit Ihrer Nachricht aus. Das stoppt das Programm sofort und zeigt Ihnen genau, wo Ihre Annahme verletzt wurde.

Wichtige Unterscheidung:

  • Validierung (mit if und raise): Zum Umgang mit erwarteten Problemen durch Benutzer oder externe Daten
  • Assertions: Zum Aufdecken von Programmierbugs während der Entwicklung
python
# Validierung – behandelt erwartete Benutzerfehler
def get_positive_number(prompt):
    while True:
        try:
            value = float(input(prompt))
            if value <= 0:
                print("Error: Please enter a positive number.")
            else:
                return value
        except ValueError:
            print("Error: Please enter a valid number.")
 
# Assertion – fängt Programmierfehler ab
def calculate_discount(price, discount_rate):
    # Diese sollten niemals verletzt werden, wenn das Programm korrekt geschrieben ist
    assert price >= 0, "price should be non-negative"
    assert 0 <= discount_rate <= 1, "discount_rate should be between 0 and 1"
    
    return price * (1 - discount_rate)

26.4.2) Funktions-Preconditions (Vorbedingungen) prüfen

Assertions sind hervorragend, um sicherzustellen, dass die Preconditions einer Funktion (Anforderungen, die wahr sein müssen, bevor die Funktion ausgeführt wird) erfüllt sind:

python
def get_list_element(items, index):
    """Get an element from a list at the specified index."""
    # Preconditions
    assert isinstance(items, list), "items must be a list"
    assert isinstance(index, int), "index must be an integer"
    assert 0 <= index < len(items), f"index {index} out of range for list of length {len(items)}"
    
    return items[index]
 
# Korrekte Verwendung
numbers = [10, 20, 30, 40]
value = get_list_element(numbers, 2)
print(f"Value: {value}")  # Output: Value: 30
 
# Programmierfehler – falscher Typ
value = get_list_element("not a list", 0)  # AssertionError: items must be a list
 
# Programmierfehler – ungültiger Index
value = get_list_element(numbers, 10)  # AssertionError: index 10 out of range for list of length 4

Diese Assertions helfen dabei, Bugs während der Entwicklung zu finden. Wenn Sie versehentlich den falschen Typ oder einen ungültigen Index übergeben, sagt Ihnen die Assertion sofort, was schiefgelaufen ist.

26.4.3) Funktions-Postconditions (Nachbedingungen) prüfen

Postconditions sind Bedingungen, die nach der Ausführung einer Funktion wahr sein müssen. Assertions können verifizieren, dass Ihre Funktion gültige Ergebnisse produziert hat:

python
def calculate_percentage(part, whole):
    """Calculate what percentage 'part' is of 'whole'."""
    # Preconditions
    assert whole > 0, "whole must be positive"
    assert part >= 0, "part must be non-negative"
    
    # Prozent berechnen
    percentage = (part / whole) * 100
    
    # Postcondition – Ergebnis sollte ein gültiger Prozentwert sein
    assert 0 <= percentage <= 100, f"percentage {percentage} is outside valid range"
    
    return percentage
 
# Das funktioniert korrekt
percentage = calculate_percentage(25, 100)
print(f"Percentage: {percentage}%")  # Output: Percentage: 25.0%
 
# Das deckt einen Logikfehler in unserer Funktion auf
# (wir haben nicht geprüft, dass part <= whole)
percentage = calculate_percentage(150, 100)  # AssertionError: percentage 150.0 is outside valid range

Die Postcondition-Assertion hat einen Bug in unserer Funktion gefunden – wir haben vergessen zu validieren, dass part whole nicht überschreitet. Genau dafür sind Assertions da: Programmierfehler aufzudecken.

26.4.4) Assertions können deaktiviert werden

Ein wichtiges Merkmal von Assertions ist, dass sie deaktiviert werden können, wenn Python mit dem Flag -O (optimize) ausgeführt wird:

python
# Diese Datei heißt test_assertions.py
def divide(a, b):
    assert b != 0, "divisor cannot be zero"
    return a / b
 
result = divide(10, 2)
print(f"Result: {result}")
 
result = divide(10, 0)  # AssertionError when assertions are enabled

Normal ausführen:

bash
python test_assertions.py
# Output: Result: 5.0
# Then: AssertionError: divisor cannot be zero

Mit Optimierung ausführen:

bash
python -O test_assertions.py
# Output: Result: 5.0
# Then: ZeroDivisionError: division by zero

Deshalb sollten Assertions niemals zur Validierung externer Daten verwendet werden – wenn jemand Ihr Programm mit -O ausführt, werden alle Assertions übersprungen. Verwenden Sie Assertions nur, um Programmierbugs während Entwicklung und Tests zu finden.

Bedingung True

Bedingung False

Codeausführung

Assertion-Prüfung

Ausführung fortsetzen

AssertionError auslösen
mit Nachricht

Programm stoppt
zeigt Traceback

Entwickler behebt Bug

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