Python & AI Tutorials Logo
Python Programmierung

41. Debugging und Testen Ihres Codes

Code zu schreiben ist nur die halbe Miete. Die andere Hälfte besteht darin, sicherzustellen, dass Ihr Code korrekt funktioniert und Probleme zu finden, wenn er es nicht tut. Jeder Programmierer, von Anfängern bis zu Experten, schreibt Code mit Bugs. Der Unterschied ist, dass erfahrene Programmierer systematische Vorgehensweisen entwickelt haben, um diese Bugs zu finden und zu beheben.

In diesem Kapitel lernen Sie praktische Debugging-Techniken, die Ihnen helfen zu verstehen, was Ihr Code tatsächlich tut, Probleme schnell zu lokalisieren und zu überprüfen, dass Ihr Code wie beabsichtigt funktioniert. Diese Fähigkeiten machen Sie zu einem sichereren und effektiveren Programmierer.

41.1) Tracebacks lesen, um Fehler zu lokalisieren (Schneller Überblick)

Wie wir in Kapitel 24 gelernt haben, liefert Python detaillierte Fehlermeldungen namens Tracebacks, wenn etwas schiefgeht. Sehen wir uns an, wie man sie effektiv liest, da dies Ihre erste Verteidigungslinie beim Debugging ist.

41.1.1) Die Anatomie eines Tracebacks

Wenn Python auf einen Fehler stößt, zeigt es Ihnen genau, wo das Problem aufgetreten ist und um welchen Fehlertyp es sich handelt. Hier ist ein typischer Traceback:

python
def calculate_average(numbers):
    total = sum(numbers)
    count = len(numbers)
    return total / count
 
def process_student_grades(grades):
    average = calculate_average(grades)
    return f"Average: {average:.1f}"
 
# Das wird einen Fehler verursachen
student_grades = []
result = process_student_grades(student_grades)
print(result)

Ausgabe:

Traceback (most recent call last):
  File "grades.py", line 12, in <module>
    result = process_student_grades(student_grades)
  File "grades.py", line 7, in process_student_grades
    average = calculate_average(grades)
  File "grades.py", line 4, in calculate_average
    return total / count
           ~~~~~~^~~~~~~
ZeroDivisionError: division by zero

Zerlegen wir, was uns dieser Traceback sagt:

Zeile 12: process_student_grades aufgerufen

Zeile 7: calculate_average aufgerufen

Zeile 4: Divisionsoperation

ZeroDivisionError: division by zero

Von unten nach oben lesen:

  1. Der Fehlertyp und die Meldung (unten): ZeroDivisionError: division by zero sagt uns genau, was schiefgelaufen ist
  2. Die genaue Zeile, in der der Fehler aufgetreten ist: return total / count in Zeile 4
  3. Die Aufrufkette zeigt, wie wir dorthin gekommen sind: Start in Zeile 12, über Zeile 7, Ende in Zeile 4

41.1.2) Tracebacks verwenden, um die Grundursache zu finden

Der Traceback zeigt Ihnen das Symptom (wo der Fehler aufgetreten ist), aber Sie müssen die Ursache finden (warum er aufgetreten ist). Verfolgen wir das Problem:

python
# Der Fehler passiert hier
return total / count  # count ist 0
 
# Aber das eigentliche Problem ist hier
student_grades = []  # Leere Liste wird an die Funktion übergeben

Die Division durch null passiert, weil wir eine leere Liste übergeben haben. Der Traceback zeigt auf Zeile 4, aber die Korrektur muss früher passieren—entweder durch Validierung der Eingabe oder durch Behandlung des Falls einer leeren Liste:

python
def calculate_average(numbers):
    """Return the average of numbers, or None if the list is empty."""
    if not numbers:
        return None
    return sum(numbers) / len(numbers)
 
def process_student_grades(grades):
    """Process student grades and return a formatted string."""
    average = calculate_average(grades)
    if average is None:
        return "No grades to process"
    return f"Average: {average:.1f}"
 
# Jetzt funktioniert das sicher
student_grades = []
result = process_student_grades(student_grades)
print(result)  # Output: No grades to process
 
# Und das funktioniert auch
student_grades = [85, 92, 78, 90]
result = process_student_grades(student_grades)
print(result)  # Output: Average: 86.2

Wichtige Erkenntnisse:

  • Lesen Sie Tracebacks von unten nach oben
  • Die Fehlerstelle (Symptom) ist nicht immer die Grundursache
  • Validieren Sie Eingaben frühzeitig, um spätere Fehler zu verhindern
  • Verwenden Sie defensive Programmierung (.get(), Längenprüfungen) für sichereren Code

Verschiedene Fehlertypen erzeugen unterschiedliche Tracebacks, aber der Leseprozess ist immer derselbe: Beginnen Sie unten, um zu sehen, was schiefgelaufen ist, und verfolgen Sie dann nach oben, um zu verstehen, wie Sie dorthin gekommen sind. Wenn Sie eine Auffrischung zu bestimmten Exception-Typen brauchen, schauen Sie in Kapitel 24 nach.

Jetzt, da Sie Tracebacks effektiv lesen können, lernen wir als Nächstes, wie Sie Ihren Code mental nachverfolgen, um Schritt für Schritt zu verstehen, was er tut.

41.2) Codeausführung mental nachverfolgen

Manchmal stoßen Sie auf einen Bug, können den Code aber nicht sofort ausführen—vielleicht prüfen Sie Code auf Papier, lesen den Pull Request einer anderen Person oder versuchen zu verstehen, warum sich eine Funktion unerwartet verhält. In diesen Situationen wird mentales Ausführen—Code Zeile für Zeile im Kopf durchzugehen und nachzuverfolgen, was mit jeder Variablen passiert—unverzichtbar.

Selbst erfahrene Programmierer nutzen diese Technik regelmäßig. Bevor sie Print-Statements hinzufügen oder einen Debugger starten, verfolgen sie oft ein paar Iterationen mental, um eine Hypothese zu bilden, wo das Problem liegen könnte. Das ist schneller als Trial-and-Error und hilft Ihnen, Ihren Code tiefer zu verstehen.

Mentales Ausführen ist besonders nützlich, wenn:

  • Sie unbekannten Code lesen, um zu verstehen, was er tut
  • Sie kleine Funktionen (5–15 Zeilen) prüfen, bevor Sie sie ausführen
  • Sie Logikfehler debuggen, bei denen der Code läuft, aber falsche Ergebnisse liefert
  • Sie Schleifenverhalten verstehen wollen, wenn das Muster nicht sofort offensichtlich ist
  • Sie Code-Reviews machen, bei denen Sie den Code nicht einfach selbst ausführen können

Bei größerem oder komplexerem Code kombinieren Sie mentales Nachverfolgen mit anderen Techniken, die wir später in diesem Kapitel behandeln. Aber diese Fähigkeit zu beherrschen, macht Sie zu einem wesentlich effektiveren Debugger.

41.2.1) Der Prozess des mentalen Ausführens

Wenn Sie Code mental ausführen, handeln Sie wie der Python-Interpreter und folgen denselben Regeln, denen Python folgt. Üben wir das mit einem einfachen Beispiel:

python
def find_maximum(numbers):
    max_value = numbers[0]
    for num in numbers:
        if num > max_value:
            max_value = num
    return max_value
 
result = find_maximum([3, 7, 2, 9, 5])
print(result)  # Output: 9

So können Sie diesen Code nachverfolgen:

Schritt-für-Schritt-Trace:

Initial state:
  numbers = [3, 7, 2, 9, 5]
  max_value = 3  (numbers[0])
 
Iteration 1: num = 3
  Check: 3 > 3? → False
  max_value remains 3
 
Iteration 2: num = 7
  Check: 7 > 3? → True
  max_value = 7 ✓
 
Iteration 3: num = 2
  Check: 2 > 7? → False
  max_value remains 7
 
Iteration 4: num = 9
  Check: 9 > 7? → True
  max_value = 9 ✓
 
Iteration 5: num = 5
  Check: 5 > 9? → False
  max_value remains 9
 
Return: 9

41.2.2) Eine Trace-Tabelle erstellen

Für komplexeren Code erstellen Sie eine Trace-Tabelle, die zeigt, wie sich Variablen im Zeitverlauf ändern. Das ist besonders hilfreich für Schleifen(loops) und verschachtelte Strukturen:

python
def calculate_running_totals(numbers):
    totals = []
    running_sum = 0
    for num in numbers:
        running_sum += num
        totals.append(running_sum)
    return totals
 
result = calculate_running_totals([10, 20, 30, 40])
print(result)  # Output: [10, 30, 60, 100]

Trace-Tabelle:

Die Tabelle zeigt den Zustand der Variablen in jedem Schritt. Beachten Sie, wie sich running_sum von „vorher“ zu „nachher“ bei jeder Addition ändert:

Iterationnumrunning_sum (vorher)running_sum (nachher)totals
Start-00[]
110010[10]
2201030[10, 30]
3303060[10, 30, 60]
44060100[10, 30, 60, 100]

Das Erstellen dieser Tabelle hilft Ihnen zu sehen, wie genau die Daten durch Ihren Code fließen. Wenn die Ausgabe nicht mit Ihren Erwartungen übereinstimmt, können Sie exakt bestimmen, wo etwas schiefgeht.

41.2.3) Bedingungslogik nachverfolgen

Bedingte Anweisungen erfordern sorgfältige Aufmerksamkeit dafür, welche Zweige ausgeführt werden. Verfolgen wir ein komplexeres Beispiel:

python
def categorize_grade(score):
    if score >= 90:
        category = "Excellent"
        bonus = 10
    elif score >= 80:
        category = "Good"
        bonus = 5
    elif score >= 70:
        category = "Satisfactory"
        bonus = 0
    else:
        category = "Needs Improvement"
        bonus = 0
    
    final_score = score + bonus
    return category, final_score
 
result = categorize_grade(85)
print(result)  # Output: ('Good', 90)

Mentales Nachverfolgen für score = 85:

  1. Prüfen 85 >= 90 → False, ersten Block überspringen
  2. Prüfen 85 >= 80 → True, zweiten Block betreten
  3. Setzen category = "Good" und bonus = 5
  4. Verbleibende elif- und else-Blöcke überspringen (Treffer bereits gefunden)
  5. Berechnen final_score = 85 + 5 = 90
  6. Rückgabe ("Good", 90)

41.2.4) Funktionsaufrufe und Rückgaben nachverfolgen

Wenn Funktionen andere Funktionen aufrufen, müssen Sie den Call Stack(call stack) nachverfolgen—die Abfolge von Funktionsaufrufen und deren lokalen Variablen:

python
def calculate_tax(amount, rate):
    tax = amount * rate
    return tax
 
def calculate_total(price, quantity, tax_rate):
    subtotal = price * quantity
    tax = calculate_tax(subtotal, tax_rate)
    total = subtotal + tax
    return total
 
result = calculate_total(50, 3, 0.08)
print(f"Total: ${result:.2f}")  # Output: Total: $162.00

Trace mit Call Stack:

┌─ calculate_total(50, 3, 0.08)
│  price = 50, quantity = 3, tax_rate = 0.08
│  subtotal = 150

│  ┌─ calculate_tax(150, 0.08)
│  │  amount = 150, rate = 0.08
│  │  tax = 12.0
│  │  return 12.0
│  └─

│  tax = 12.0 (from calculate_tax)
│  total = 162.0
│  return 162.0
└─
 
result = 162.0

Diese Schritt-für-Schritt-Nachverfolgung zeigt genau, wie Daten zwischen Funktionen fließen. Beim Debugging können Sie, wenn das Endergebnis falsch ist, zurückverfolgen, welche Funktion einen falschen Zwischenwert erzeugt hat.

Mentales Nachverfolgen ist mächtig, aber bei komplexem Code kann es mühsam sein. Im nächsten Abschnitt lernen wir, wie Sie Print-Statements strategisch verwenden, um zu sehen, was tatsächlich passiert, während Ihr Code läuft, was oft schneller und zuverlässiger ist als mentales Ausführen allein.

41.3) Debugging mit Print: f"{var=}" und repr()

Während mentales Ausführen bei kleinen Funktionen gut funktioniert, wird es bei größerem oder komplexerem Code unpraktisch. Wenn Sie nicht sicher sind, was in einer Schleife(loop) passiert, oder wenn eine Berechnung unerwartete Ergebnisse liefert, ist der schnellste Weg zur Untersuchung oft, strategische print()-Statements hinzuzufügen.

Print-Debugging hat gegenüber anderen Techniken einige Vorteile:

  • Keine speziellen Tools nötig: Funktioniert in jeder Python-Umgebung
  • Schnell umzusetzen: Fügen Sie in Sekunden ein Print-Statement hinzu
  • Klare Ausgabe: Sie sehen genau, wonach Sie gefragt haben
  • Einfach zu entfernen: Löschen Sie die Prints, wenn Sie fertig sind

Professionelle Entwickler nutzen Print-Debugging ständig—es ist keine „Anfänger“-Technik. Lernen wir, wie man es effektiv einsetzt.

41.3.1) Einfaches Print-Debugging

Der einfachste Debugging-Ansatz ist, Variablenwerte an wichtigen Stellen in Ihrem Code auszugeben:

python
def process_order(items, discount_rate):
    print(f"Starting process_order")
    print(f"Items: {items}")
    print(f"Discount rate: {discount_rate}")
    
    subtotal = sum(item['price'] * item['quantity'] for item in items)
    print(f"Subtotal: {subtotal}")
    
    discount = subtotal * discount_rate
    print(f"Discount amount: {discount}")
    
    total = subtotal - discount
    print(f"Final total: {total}")
    
    return total
 
order_items = [
    {'name': 'Book', 'price': 25.99, 'quantity': 2},
    {'name': 'Pen', 'price': 3.50, 'quantity': 5}
]
 
result = process_order(order_items, 0.10)

Ausgabe:

Starting process_order
Items: [{'name': 'Book', 'price': 25.99, 'quantity': 2}, {'name': 'Pen', 'price': 3.5, 'quantity': 5}]
Discount rate: 0.1
Subtotal: 69.47999999999999
Discount amount: 6.9479999999999995
Final total: 62.53199999999999

Diese Print-Statements zeigen Ihnen den Ablauf der Ausführung und die Werte in jedem Schritt. Wenn das Endergebnis falsch ist, sehen Sie genau, wo die Berechnung aus dem Ruder gelaufen ist.

41.3.2) f"{var=}" für schnelle Inspektion verwenden

Python 3.8 hat eine praktische Debugging-Syntax eingeführt: f"{var=}". Diese gibt sowohl den Variablennamen als auch seinen Wert aus:

python
def calculate_compound_interest(principal, rate, years):
    # Traditioneller Ansatz
    print(f"principal: {principal}")
    print(f"rate: {rate}")
    print(f"years: {years}")
    
    # Sauberer Ansatz mit f"{var=}"
    print(f"{principal=}")
    print(f"{rate=}")
    print(f"{years=}")
    
    # Sie können Ausdrücke verwenden, nicht nur Variablen
    print(f"{principal * rate=}")
    print(f"{(1 + rate) ** years=}")
    
    amount = principal * (1 + rate) ** years
    print(f"{amount=}")
    
    return amount
 
result = calculate_compound_interest(1000, 0.05, 10)

Ausgabe:

principal: 1000
rate: 0.05
years: 10
principal=1000
rate=0.05
years=10
principal * rate=50.0
(1 + rate) ** years=1.628894626777442
amount=1628.894626777442

41.3.3) repr() verwenden, um die echte Form von Daten zu sehen

Manchmal ist das, was Sie gedruckt sehen, nicht das, was Sie glauben. Die Funktion repr() zeigt Ihnen die exakte Repräsentation eines Objekts, einschließlich versteckter Zeichen:

python
# Diese Strings sehen beim Ausgeben gleich aus
text1 = "Hello"
text2 = "Hello\n"  # Hat am Ende einen Zeilenumbruch
 
print("Using print():")
print(f"text1: {text1}")
print(f"text2: {text2}")
 
print("\nUsing repr():")
print(f"text1: {repr(text1)}")
print(f"text2: {repr(text2)}")

Ausgabe:

Using print():
text1: Hello
text2: Hello
 
Using repr():
text1: 'Hello'
text2: 'Hello\n'

Die repr()-Ausgabe zeigt, dass text2 ein verstecktes Newline-Zeichen enthält. Das ist entscheidend beim Debugging von String-Verarbeitung:

python
def clean_user_input():
    # Benutzereingaben enthalten oft versteckten Whitespace
    username = input("Enter username: ")  # User types "Alice  "
    
    print(f"Username with print(): {username}")
    print(f"Username with repr(): {repr(username)}")
    
    # Eingabe bereinigen
    cleaned = username.strip()
    print(f"Cleaned with repr(): {repr(cleaned)}")
    
    return cleaned

Wenn ein Benutzer „Alice“ eingibt, danach Leerzeichen, und Enter drückt, könnten Sie Folgendes sehen:

Ausgabe:

Enter username: Alice  
Username with print(): Alice  
Username with repr(): 'Alice  '
Cleaned with repr(): 'Alice'

Die repr()-Ausgabe zeigt die nachgestellten Leerzeichen, die print() nicht klar sichtbar macht.

Wann repr() vs str() verwenden:

repr() ist für Entwickler gedacht—es zeigt die „offizielle“ String-Repräsentation, mit der man das Objekt wiederherstellen könnte. str() (das print() standardmäßig verwendet) ist für Endnutzer gedacht—es zeigt eine lesbare, freundliche Version.

Für Debugging ist repr() meist hilfreicher, weil es die wahre Struktur Ihrer Daten sichtbar macht.

41.3.4) Strategische Platzierung von Prints

Verteilen Sie nicht einfach überall Print-Statements. Platzieren Sie sie strategisch:

python
def calculate_shipping_cost(weight, distance, express=False):
    print(f"=== calculate_shipping_cost called ===")
    print(f"Input: {weight=}, {distance=}, {express=}")
    
    # Basiskosten berechnen
    base_rate = 0.50
    base_cost = weight * distance * base_rate
    print(f"Calculated: {base_cost=}")
    
    # Express-Zuschlag anwenden
    if express:
        surcharge = base_cost * 0.50
        print(f"Express surcharge: {surcharge=}")
        total = base_cost + surcharge
    else:
        print("No express surcharge")
        total = base_cost
    
    print(f"Final: {total=}")
    print(f"=== calculate_shipping_cost returning ===\n")
    return total
 
# Verschiedene Szenarien testen
cost1 = calculate_shipping_cost(10, 500, express=True)
cost2 = calculate_shipping_cost(5, 200, express=False)

Ausgabe:

=== calculate_shipping_cost called ===
Input: weight=10, distance=500, express=True
Calculated: base_cost=2500.0
Express surcharge: surcharge=1250.0
Final: total=3750.0
=== calculate_shipping_cost returning ===
 
=== calculate_shipping_cost called ===
Input: weight=5, distance=200, express=False
Calculated: base_cost=500.0
No express surcharge
Final: total=500.0
=== calculate_shipping_cost returning ===

Die klaren Marker (===) und die organisierte Ausgabe machen es leicht, dem Ausführungsfluss zu folgen.

41.3.5) Debug-Prints entfernen

Sobald Sie den Bug gefunden und behoben haben, denken Sie daran, Ihre Debug-Prints zu entfernen. Hier sind einige Strategien:

Strategie 1: Ein eindeutiges Präfix verwenden

python
# Einfach per Suchen/Ersetzen zu finden und zu entfernen
print(f"DEBUG: {total=}")
print(f"DEBUG: {items=}")

Strategie 2: Ein Debug-Flag verwenden

python
DEBUG = True
 
def calculate_total(items):
    if DEBUG:
        print(f"Processing {len(items)} items")
    
    total = sum(item['price'] for item in items)
    
    if DEBUG:
        print(f"{total=}")
    
    return total
 
# Alle Debug-Ausgaben auf einmal ausschalten
DEBUG = False

Strategie 3: Auskommentieren, aber behalten

python
def process_data(data):
    # print(f"DEBUG: {data=}")  # Useful for future debugging
    result = transform(data)
    # print(f"DEBUG: {result=}")
    return result

Für ausgefeilteres Logging, das Sie in Produktionscode belassen können, hat Python ein logging-Modul, aber einfache Print-Statements sind perfekt für schnelles Debugging während der Entwicklung.

Print-Debugging zeigt Ihnen die Werte von Variablen, aber manchmal müssen Sie die Struktur eines Objekts verstehen—welche Methoden es hat, welchen Typ es ist und was es kann. Im nächsten Abschnitt lernen wir, wie Sie Objekte mit type() und dir() inspizieren.

41.4) Objekte inspizieren: type() und dir()

Print-Debugging zeigt Ihnen die Werte Ihrer Variablen, aber manchmal ist nicht der Wert das Problem—sondern der Typ des Objekts, mit dem Sie arbeiten. Sie erwarten vielleicht eine Liste(list), bekommen aber einen String, oder Sie arbeiten mit einem unbekannten Objekt und wissen nicht, welche Methoden es unterstützt.

Python bietet eingebaute Werkzeuge zum Inspizieren von Objekten: type() sagt Ihnen, welche Art von Objekt Sie haben, und dir() zeigt Ihnen, welche Operationen es unterstützt. Diese Funktionen sind essenziell, wenn:

  • Sie typbezogene Fehler debuggen (TypeError, AttributeError)
  • Sie mit unbekannten Libraries oder APIs arbeiten
  • Sie Objekte verstehen möchten, die von Third-Party-Code zurückgegeben werden
  • Sie überprüfen möchten, dass Ihr Code die erwarteten Typen erhält

Lernen wir, wie Sie diese Inspektionswerkzeuge effektiv einsetzen.

41.4.1) type() verwenden, um Objekttypen zu identifizieren

Die Funktion type() sagt Ihnen genau, welche Art von Objekt Sie haben. Das ist entscheidend beim Debugging typbezogener Fehler:

python
def process_data(data):
    print(f"Received data: {data}")
    print(f"Data type: {type(data)}")
    
    if isinstance(data, list):
        print("Processing as list")
        return sum(data)
    elif isinstance(data, dict):
        print("Processing as dictionary")
        return sum(data.values())
    else:
        print("Unexpected type!")
        return None
 
# Mit verschiedenen Typen testen
result1 = process_data([10, 20, 30])
print(f"Result: {result1}\n")
 
result2 = process_data({'a': 10, 'b': 20, 'c': 30})
print(f"Result: {result2}\n")
 
result3 = process_data("123")
print(f"Result: {result3}")

Ausgabe:

Received data: [10, 20, 30]
Data type: <class 'list'>
Processing as list
Result: 60
 
Received data: {'a': 10, 'b': 20, 'c': 30}
Data type: <class 'dict'>
Processing as dictionary
Result: 60
 
Received data: 123
Data type: <class 'str'>
Unexpected type!
Result: None

41.4.2) Typverwechslungen debuggen

Typverwechslungen sind eine häufige Bug-Quelle, besonders wenn Sie mit Funktionen arbeiten, die Daten aus mehreren Quellen erhalten können—Benutzereingaben, Dateilesen, API-Antworten oder andere Funktionen. Sie erwarten vielleicht eine Liste von Zahlen, erhalten aber versehentlich einen String, oder erwarten ein Dictionary, bekommen aber eine Liste.

Mit type() können Sie feststellen, wann Sie den falschen Typ haben. Indem Sie den Typ früh in Ihrer Funktion ausgeben, können Sie Typ-Mismatches sofort erkennen, bevor sie weiter unten in Ihrem Code verwirrende Fehlermeldungen verursachen:

python
def calculate_average(numbers):
    print(f"{type(numbers)=}")
    print(f"{numbers=}")  # Zeigen, was wir tatsächlich bekommen haben
    
    # Das schlägt fehl, wenn numbers keine Liste von Zahlen ist
    total = sum(numbers)
    count = len(numbers)
    return total / count
 
# Häufiger Fehler: vergessen, String in Liste umzuwandeln
scores = "85"  # Should be [85] or just 85
try:
    avg = calculate_average(scores)
    print(f"Average: {avg}")
except TypeError as e:
    print(f"TypeError: {e}")
    print(f"Expected list of numbers, got {type(scores)}")
    print(f"The string contains: {repr(scores)}")

Ausgabe:

type(numbers)=<class 'str'>
numbers='85'
TypeError: unsupported operand type(s) for +: 'int' and 'str'
Expected list of numbers, got <class 'str'>
The string contains: '85'

Die type()-Prüfung zeigt das Problem sofort: Wir haben einen String übergeben, obwohl wir eine Liste brauchten. Ohne diese Debug-Ausgabe hätten Sie möglicherweise Zeit damit verbracht zu verstehen, warum sum() fehlgeschlagen ist, obwohl das eigentliche Problem ist, dass der falsche Datentyp überhaupt erst in die Funktion gelangt ist.

41.4.3) dir() verwenden, um verfügbare Methoden zu entdecken

Wenn Sie mit unbekannten Objekten arbeiten—sei es aus einer Library, die Sie lernen, einer API-Antwort oder sogar Python-Built-ins—müssen Sie oft wissen: „Was kann ich mit diesem Objekt tun?“ Die Funktion dir() beantwortet diese Frage, indem sie alle Attribute und Methoden auflistet, die auf einem Objekt verfügbar sind.

Das ist besonders wertvoll, wenn:

  • Sie eine neue Library erkunden und sehen wollen, welche Methoden ein Objekt bereitstellt
  • Sie ein Objekt aus Third-Party-Code erhalten und seine Fähigkeiten verstehen müssen
  • Sie den genauen Namen einer Methode vergessen haben, die Sie verwenden möchten
  • Sie beim Debugging prüfen wollen, ob ein Objekt die erwarteten Methoden hat

Sehen wir uns an, welche Methoden ein String hat:

python
# Erkunden, welche Methoden ein String hat
text = "Python Programming"
 
print(f"Type: {type(text)}")
print(f"\nAvailable string methods (showing first 10):")
methods = [m for m in dir(text) if not m.startswith('_')]
for method in methods[:10]:  # Erste 10 anzeigen
    print(f"  {method}")
print(f"  ... and {len(methods) - 10} more")

Ausgabe:

Type: <class 'str'>
 
Available string methods (showing first 10):
  capitalize
  casefold
  center
  count
  encode
  endswith
  expandtabs
  find
  format
  format_map
  ... and 37 more

Jetzt sehen Sie alle Operationen, die für Strings verfügbar sind. Wenn Sie nicht sicher waren, ob Strings eine count-Methode oder eine endswith-Methode haben, zeigt dir() Ihnen, dass sie existieren. Sie können dann Pythons help()-Funktion verwenden, um mehr über eine bestimmte Methode zu erfahren:

python
# Mehr über eine bestimmte Methode lernen
help(text.count)

Das zeigt Ihnen die Dokumentation für die count-Methode:

Help on built-in function count:
 
count(sub[, start[, end]], /) method of builtins.str instance
    Return the number of non-overlapping occurrences of substring sub in string S[start:end].
 
    Optional arguments start and end are interpreted as in slice notation.

Die Funktion dir() ist, als hätten Sie Dokumentation direkt in Python eingebaut—sie zeigt Ihnen, was mit jedem Objekt möglich ist, mit dem Sie arbeiten.

41.4.4) Eigene Objekte inspizieren

Wenn Sie mit eigenen Klassen arbeiten, helfen type() und dir() Ihnen zu verstehen, womit Sie es zu tun haben. Zusätzlich stellt Python hasattr() bereit, um zu prüfen, ob ein Objekt ein bestimmtes Attribut hat, bevor Sie versuchen, darauf zuzugreifen—das verhindert AttributeError-Exceptions.

python
class Student:
    def __init__(self, name, grade):
        self.name = name
        self.grade = grade
    
    def get_status(self):
        return "Passing" if self.grade >= 60 else "Failing"
 
student = Student("Alice", 85)
 
print(f"Object type: {type(student)}")
print(f"\nAvailable attributes and methods:")
for attr in dir(student):
    if not attr.startswith('_'):
        print(f"  {attr}")
 
# Prüfen, ob bestimmte Attribute existieren
print(f"\nHas 'name' attribute: {hasattr(student, 'name')}")
print(f"Has 'age' attribute: {hasattr(student, 'age')}")
print(f"Has 'get_status' method: {hasattr(student, 'get_status')}")
 
# Jetzt können wir sicher auf Attribute zugreifen, von denen wir wissen, dass sie existieren
if hasattr(student, 'name'):
    print(f"\nStudent name: {student.name}")
else:
    print("\nNo name attribute found")
 
if hasattr(student, 'get_status'):
    print(f"Status: {student.get_status()}")
else:
    print("No get_status method found")
 
# Das verhindert Fehler wie diesen:
# print(student.age)  # Would raise AttributeError!

Ausgabe:

Object type: <class '__main__.Student'>
 
Available attributes and methods:
  get_status
  grade
  name
 
Has 'name' attribute: True
Has 'age' attribute: False
Has 'get_status' method: True
 
Student name: Alice
Status: Passing

Die Funktion hasattr() ist essenziell, um defensiven Code zu schreiben—Code, der prüft, ob Operationen sicher sind, bevor er sie ausführt. Die Funktion gibt True zurück, wenn das Attribut existiert, und False, wenn nicht—sodass Sie Entscheidungen treffen können, bevor Sie versuchen, auf Attribute zuzugreifen. Das ist besonders wichtig, wenn Sie mit Objekten aus externen Libraries oder mit Benutzereingaben arbeiten, bei denen Sie nicht garantieren können, welche Attribute vorhanden sind.

41.4.5) getattr() für sicheren Attributzugriff verwenden

Wenn Sie nicht sicher sind, ob ein Attribut existiert, verwenden Sie getattr() mit einem Standardwert:

python
def display_student_info(student):
    """Safely display student info even if some attributes are missing."""
    print(f"Type: {type(student)}")
    
    # Sicherer Attributzugriff mit Standardwerten
    name = getattr(student, 'name', 'Unknown')
    grade = getattr(student, 'grade', 0)
    age = getattr(student, 'age', 'Not specified')
    
    print(f"Name: {name}")
    print(f"Grade: {grade}")
    print(f"Age: {age}")
    
    # Prüfen, ob die Methode existiert, bevor sie aufgerufen wird
    if hasattr(student, 'get_status'):
        status = student.get_status()
        print(f"Status: {status}")
 
# Verwendung derselben Student-Klasse wie oben
student = Student("Bob", 72)
display_student_info(student)

Ausgabe:

Type: <class '__main__.Student'>
Name: Bob
Grade: 72
Age: Not specified
Status: Passing

Dieser Ansatz verhindert AttributeError-Exceptions, wenn Sie mit Objekten arbeiten, die möglicherweise nicht alle erwarteten Attribute haben. Die Funktion getattr() ist besonders nützlich, wenn:

  • Sie mit Objekten aus externen APIs arbeiten, die unterschiedliche Versionen haben können
  • Sie optionale Attribute in Ihren eigenen Klassen behandeln
  • Sie defensiven Code bauen, der fehlende Daten sauber verarbeitet

Zu verstehen, welchen Typ ein Objekt hat und welche Methoden es unterstützt, ist entscheidend fürs Debugging. Aber manchmal müssen Sie nicht nur verifizieren, dass Ihr Code läuft, sondern dass er die korrekten Ergebnisse produziert. Im nächsten Abschnitt lernen wir, wie Sie assert-Anweisungen verwenden, um Ihre Annahmen zu testen und Bugs früh zu entdecken.

41.5) Testen mit assert-Anweisungen

Wir haben gelernt, wie man Code debuggt, wenn etwas schiefgeht—Tracebacks lesen, Ausführung mental nachverfolgen, Print-Statements verwenden und Objekte inspizieren. Aber es gibt einen besseren Ansatz, als Bugs zu beheben, nachdem sie aufgetreten sind: sie von vornherein durch Tests zu verhindern.

Die assert-Anweisung ist Pythons einfachstes Testwerkzeug. Sie erlaubt Ihnen zu prüfen, dass Ihr Code sich korrekt verhält, indem Sie Annahmen an kritischen Stellen überprüfen. Wenn eine Assertion fehlschlägt, sagt Python Ihnen sofort genau, was schiefgelaufen ist und wo, was es deutlich einfacher macht, Bugs früh zu erkennen—oft bevor Sie überhaupt Ihr Hauptprogramm ausführen.

Assertions sind besonders wertvoll für:

  • Zu verifizieren, dass Funktionen erwartete Ergebnisse liefern
  • Zu prüfen, dass Eingaben Ihre Anforderungen erfüllen
  • Randfälle zu testen, die Ihren Code brechen könnten
  • Annahmen zu dokumentieren, auf die sich Ihr Code stützt

Betrachten Sie Assertions als automatisierte Checks, die kontinuierlich verifizieren, dass Ihr Code wie beabsichtigt funktioniert. Lernen wir, wie man sie effektiv nutzt.

41.5.1) Was assert macht

Eine assert-Anweisung prüft, ob eine Bedingung wahr ist. Wenn die Bedingung wahr ist, passiert nichts—der Code läuft normal weiter. Wenn sie falsch ist, löst Python einen AssertionError aus und stoppt die Ausführung.

Syntax:

python
assert condition, "Optional error message"
  • condition: Ein Ausdruck, der zu True oder False ausgewertet wird
  • "Optional error message": Hilfreicher Text, der angezeigt wird, wenn die Assertion fehlschlägt

So funktioniert das in der Praxis:

python
# Einfache Assertions
x = 10
assert x > 0  # Läuft still durch (x ist tatsächlich > 0)
assert x < 5  # Schlägt fehl! Löst AssertionError aus
 
# Mit Fehlermeldungen (viel hilfreicher!)
assert x > 0, f"x must be positive, got {x}"
assert x < 5, f"x must be less than 5, got {x}"  # Schlägt mit klarer Meldung fehl

Sehen wir uns jetzt Assertions in einer echten Funktion an:

python
def calculate_discount(price, discount_percent):
    # Verifizieren, dass Eingaben gültig sind
    assert price >= 0, "Price cannot be negative"
    assert 0 <= discount_percent <= 100, "Discount must be between 0 and 100"
    
    discount_amount = price * (discount_percent / 100)
    final_price = price - discount_amount
    
    # Verifizieren, dass die Ausgabe sinnvoll ist
    assert final_price >= 0, "Final price cannot be negative"
    
    return final_price
 
# Gültige Eingaben funktionieren problemlos
result = calculate_discount(100, 20)
print(f"Price after 20% discount: ${result}")  # Output: Price after 20% discount: $80.0
 
# Ungültige Eingaben lösen Assertions aus
try:
    result = calculate_discount(-50, 20)
except AssertionError as e:
    print(f"Assertion failed: {e}")  # Output: Assertion failed: Price cannot be negative
 
try:
    result = calculate_discount(100, 150)
except AssertionError as e:
    print(f"Assertion failed: {e}")  # Output: Assertion failed: Discount must be between 0 and 100

41.5.2) Assertions verwenden, um Funktionsverhalten zu verifizieren

Assertions eignen sich hervorragend, um zu testen, dass Funktionen erwartete Ergebnisse liefern:

python
def calculate_average(numbers):
    if not numbers:
        return 0.0
    return sum(numbers) / len(numbers)
 
# Mit verschiedenen Eingaben testen
result = calculate_average([10, 20, 30])
assert result == 20.0, f"Expected 20.0, got {result}"
print(f"Test 1 passed: average of [10, 20, 30] = {result}")
 
result = calculate_average([5, 5, 5, 5])
assert result == 5.0, f"Expected 5.0, got {result}"
print(f"Test 2 passed: average of [5, 5, 5, 5] = {result}")
 
result = calculate_average([])
assert result == 0.0, f"Expected 0.0 for empty list, got {result}"
print(f"Test 3 passed: average of [] = {result}")
 
result = calculate_average([100])
assert result == 100.0, f"Expected 100.0, got {result}"
print(f"Test 4 passed: average of [100] = {result}")

Ausgabe:

Test 1 passed: average of [10, 20, 30] = 20.0
Test 2 passed: average of [5, 5, 5, 5] = 5.0
Test 3 passed: average of [] = 0.0
Test 4 passed: average of [100] = 100.0

Wenn eine Assertion fehlschlägt, wissen Sie sofort, welcher Testfall das Problem aufgedeckt hat.

41.5.3) Randfälle testen

Randfälle(edge cases) sind Eingaben an den Grenzen dessen, was Ihre Funktion behandeln soll. Das Testen dieser Fälle deckt Bugs auf, die normale Eingaben übersehen könnten:

python
def get_first_and_last(items):
    """Return the first and last items from a sequence."""
    assert len(items) > 0, "Cannot get first and last from empty sequence"
    return items[0], items[-1]
 
# Normalfall testen
result = get_first_and_last([1, 2, 3, 4, 5])
assert result == (1, 5), f"Expected (1, 5), got {result}"
print(f"Normal case: {result}")
 
# Randfall testen: einzelnes Element
result = get_first_and_last([42])
assert result == (42, 42), f"Expected (42, 42), got {result}"
print(f"Single item: {result}")
 
# Randfall testen: zwei Elemente
result = get_first_and_last([10, 20])
assert result == (10, 20), f"Expected (10, 20), got {result}"
print(f"Two items: {result}")
 
# Randfall testen: leere Sequenz (soll fehlschlagen)
try:
    result = get_first_and_last([])
    print("ERROR: Should have raised AssertionError for empty list")
except AssertionError as e:
    print(f"Empty list correctly rejected: {e}")

Ausgabe:

Normal case: (1, 5)
Single item: (42, 42)
Two items: (10, 20)
Empty list correctly rejected: Cannot get first and last from empty sequence

41.5.4) Datentransformationen testen

Wenn Ihre Funktion Daten transformiert, prüfen Sie mit Assertions, dass die Transformation korrekt ist:

python
def remove_duplicates(items):
    """Remove duplicates while preserving order."""
    seen = set()
    result = []
    for item in items:
        if item not in seen:
            seen.add(item)
            result.append(item)
    return result
 
# Grundlegendes Entfernen von Duplikaten testen
input_data = [1, 2, 2, 3, 1, 4, 3, 5]
result = remove_duplicates(input_data)
expected = [1, 2, 3, 4, 5]
assert result == expected, f"Expected {expected}, got {result}"
print(f"Test 1 passed: {input_data} -> {result}")
 
# Testen, dass die Reihenfolge erhalten bleibt
input_data = [3, 1, 2, 1, 3, 2]
result = remove_duplicates(input_data)
expected = [3, 1, 2]
assert result == expected, f"Expected {expected}, got {result}"
print(f"Test 2 passed: {input_data} -> {result}")
 
# Testen ohne Duplikate
input_data = [1, 2, 3, 4, 5]
result = remove_duplicates(input_data)
assert result == input_data, f"Expected {input_data}, got {result}"
print(f"Test 3 passed: {input_data} -> {result}")
 
# Testen mit lauter Duplikaten
input_data = [5, 5, 5, 5]
result = remove_duplicates(input_data)
expected = [5]
assert result == expected, f"Expected {expected}, got {result}"
print(f"Test 4 passed: {input_data} -> {result}")

Ausgabe:

Test 1 passed: [1, 2, 2, 3, 1, 4, 3, 5] -> [1, 2, 3, 4, 5]
Test 2 passed: [3, 1, 2, 1, 3, 2] -> [3, 1, 2]
Test 3 passed: [1, 2, 3, 4, 5] -> [1, 2, 3, 4, 5]
Test 4 passed: [5, 5, 5, 5] -> [5]

41.5.5) Eine einfache Testfunktion erstellen

Wenn Ihr Code wächst, wird es unübersichtlich und schwer zu verwalten, Assert-Statements überall im Hauptcode zu verteilen. Ein besserer Ansatz ist, Ihre Tests in dedizierten Testfunktionen zu organisieren. Das trennt Testcode von Produktionscode und macht es einfach, alle Tests auf einmal auszuführen.

Warum dedizierte Testfunktionen verwenden?

  • Organisation: Alle Tests für eine Funktion sind an einem Ort
  • Wiederverwendbarkeit: Führen Sie Tests jederzeit aus, wenn Sie den Code ändern
  • Dokumentation: Tests zeigen, wie sich die Funktion verhalten soll
  • Debugging: Wenn ein Test fehlschlägt, wissen Sie sofort, welches Szenario kaputt ging
  • Entwicklungsworkflow: Erst testen, dann implementieren oder den Code reparieren

So sieht das in der Praxis aus:

python
def calculate_grade(score):
    """Convert numeric score to letter grade."""
    if score >= 90:
        return 'A'
    elif score >= 80:
        return 'B'
    elif score >= 70:
        return 'C'
    elif score >= 60:
        return 'D'
    else:
        return 'F'
 
def test_calculate_grade():
    """Test the calculate_grade function.
    
    This function tests all expected behaviors:
    - Each grade range (A, B, C, D, F)
    - Boundary values (90, 80, 70, 60)
    - Edge cases (just below each boundary)
    """
    print("Testing calculate_grade...")
    
    # A-Noten testen
    assert calculate_grade(95) == 'A', "95 should be A"
    assert calculate_grade(90) == 'A', "90 should be A (boundary)"
    print("  ✓ A grades: passed")
    
    # B-Noten testen
    assert calculate_grade(85) == 'B', "85 should be B"
    assert calculate_grade(80) == 'B', "80 should be B (boundary)"
    print("  ✓ B grades: passed")
    
    # C-Noten testen
    assert calculate_grade(75) == 'C', "75 should be C"
    assert calculate_grade(70) == 'C', "70 should be C (boundary)"
    print("  ✓ C grades: passed")
    
    # D-Noten testen
    assert calculate_grade(65) == 'D', "65 should be D"
    assert calculate_grade(60) == 'D', "60 should be D (boundary)"
    print("  ✓ D grades: passed")
    
    # F-Noten testen
    assert calculate_grade(55) == 'F', "55 should be F"
    assert calculate_grade(0) == 'F', "0 should be F"
    print("  ✓ F grades: passed")
    
    # Grenzwert-Randfälle testen (eins unter jeder Schwelle)
    assert calculate_grade(89) == 'B', "89 should be B (just below A)"
    assert calculate_grade(79) == 'C', "79 should be C (just below B)"
    assert calculate_grade(69) == 'D', "69 should be D (just below C)"
    assert calculate_grade(59) == 'F', "59 should be F (just below D)"
    print("  ✓ Boundary cases: passed")
    
    print("All tests passed! ✓\n")
 
# Tests ausführen
test_calculate_grade()
 
# Jetzt können Sie die Funktion mit Vertrauen verwenden
student_score = 87
grade = calculate_grade(student_score)
print(f"Student score {student_score} = Grade {grade}")

Ausgabe:

Testing calculate_grade...
  ✓ A grades: passed
  ✓ B grades: passed
  ✓ C grades: passed
  ✓ D grades: passed
  ✓ F grades: passed
  ✓ Boundary cases: passed
All tests passed! ✓
 
Student score 87 = Grade B

Vorteile dieses Ansatzes:

  1. Klare Testorganisation: Sie sehen alle Testfälle auf einen Blick
  2. Einfach auszuführen: Rufen Sie einfach test_calculate_grade() auf, wann immer Sie die Funktion ändern
  3. Progressives Feedback: Sie sehen, welche Testgruppen bestanden werden, während die Funktion läuft
  4. Selbstdokumentierend: Die Testfunktion zeigt genau, wie calculate_grade() funktionieren soll

Wann Sie Ihre Tests ausführen sollten:

  • Bevor Sie Änderungen machen: Stellen Sie sicher, dass Ihre Tests mit dem aktuellen Code bestehen
  • Nachdem Sie Änderungen gemacht haben: Verifizieren Sie, dass Sie nichts kaputt gemacht haben
  • Wenn Sie Features hinzufügen: Schreiben Sie zuerst Tests für das neue Feature (test-driven development)
  • Wenn Sie Bugs beheben: Fügen Sie einen Test hinzu, der den Bug reproduziert, und beheben Sie ihn dann

Dieses einfache Muster—Testfunktionen mit Assertions zu schreiben—ist die Grundlage professionellen Software-Testens. Mit fortschreitender Erfahrung lernen Sie Testframeworks wie pytest und unittest kennen, aber die Kernidee bleibt gleich: Schreiben Sie Funktionen, die verifizieren, dass Ihr Code korrekt funktioniert.

41.5.6) Wann Assertions vs Exceptions verwendet werden sollten

Zu verstehen, wann man Assertions gegenüber Exceptions verwenden sollte, ist entscheidend. Sie dienen grundlegend unterschiedlichen Zwecken:

Assertions sind dazu da, Bugs während der Entwicklung zu finden:

  • Sie prüfen Dinge, die niemals falsch sein sollten, wenn Ihr Code korrekt geschrieben ist
  • Sie verifizieren interne Annahmen und Logik Ihres eigenen Codes
  • Sie helfen Ihnen, Programmierfehler zu finden, während Sie Code schreiben und testen
  • Beispiel: „An dieser Stelle in meiner Funktion sollte diese Liste niemals leer sein“
  • Beispiel: „Alle Elemente in dieser Liste sollten Integers sein, weil ich sie gerade gefiltert habe“

Exceptions sind dazu da, Fehler zu behandeln, die im normalen Betrieb auftreten können:

  • Sie behandeln externe Bedingungen, die Sie nicht kontrollieren können
  • Sie behandeln Situationen, die selbst dann auftreten können, wenn Ihr Code perfekt ist
  • Sie erlauben Ihrem Programm, sich sauber zu erholen oder informativ zu scheitern
  • Beispiel: Benutzer gibt Text ein, obwohl Sie eine Zahl erwartet haben
  • Beispiel: Eine Datei, die Ihr Code öffnen will, existiert nicht
  • Beispiel: Netzwerkrequest läuft in ein Timeout

Der entscheidende Unterschied: Assertions sagen „das sollte unmöglich sein“, während Exceptions sagen „das kann passieren, und so gehen wir damit um“.

Sehen wir das in der Praxis:

python
# Beispiel 1: Funktion wird mit BENUTZEREINGABE verwendet
# Benutzer können alles eingeben, einschließlich 0
def calculate_user_ratio(numerator, denominator):
    """Calculate ratio from user-provided numbers."""
    # Benutzer könnte 0 eingeben, daher Exception-Handling verwenden
    if denominator == 0:
        raise ValueError("Denominator cannot be zero")
    
    return numerator / denominator
 
# Beispiel 2: Interne Berechnung, bei der 0 unmöglich sein sollte
def calculate_percentage(part, total):
    """Calculate what percentage 'part' is of 'total'."""
    # Wird intern aufgerufen, nachdem wir total > 0 verifiziert haben
    # Wenn total 0 ist, ist das ein Programmierfehler in unserem Code
    assert total > 0, "total must be positive - check calling code"
    
    return (part / total) * 100

Weitere Beispiele dafür, was jeweils behandelt werden sollte:

SituationAssertion verwendenException verwenden
Benutzer gibt ungültige Eingabe ein❌ Nein✅ Ja
Datei existiert nicht❌ Nein✅ Ja
Netzwerkrequest schlägt fehl❌ Nein✅ Ja
Funktion bekommt falschen Parametertyp aus Ihrem Code✅ Ja❌ Nein
Liste sollte Elemente haben, ist aber wegen Logikfehler leer✅ Ja❌ Nein
Datenstruktur ist wegen eines Bugs in einem unerwarteten Zustand✅ Ja❌ Nein
Datenbankverbindung schlägt fehl❌ Nein✅ Ja
API liefert unerwartetes Format zurück❌ Nein✅ Ja
Ihr Algorithmus liefert mathematisch unmögliches Ergebnis✅ Ja❌ Nein

Kritische Einschränkung von Assertions:

Assertions können komplett deaktiviert werden, wenn Python mit Optimierung läuft:

bash
python -O script.py  # All assert statements are ignored!

Wenn Assertions deaktiviert sind, verschwinden sie einfach—Python prüft sie dann überhaupt nicht. Das bedeutet:

  • Niemals Assertions zur Validierung von Benutzereingaben verwenden
  • Niemals Assertions für Sicherheitsprüfungen verwenden
  • Niemals Assertions für irgendetwas verwenden, das in Produktion immer funktionieren muss
python
# DANGEROUS - DON'T DO THIS:
def process_payment(amount):
    assert amount > 0, "Amount must be positive"  # WRONG! Gets disabled with -O
    # Process payment...
 
# CORRECT - DO THIS:
def process_payment(amount):
    if amount <= 0:
        raise ValueError("Amount must be positive")  # Always checked!
    # Process payment...

Zusammengefasst:

  • Assertions = „Ich prüfe meinen eigenen Code während der Entwicklung auf Bugs“

    • Denken Sie: „Das sollte unmöglich sein, wenn ich korrekt programmiert habe“
    • Sie helfen Ihnen, Fehler in Ihrer Logik zu finden
  • Exceptions = „Ich behandle Bedingungen aus der realen Welt, die tatsächlich auftreten können“

    • Denken Sie: „Das kann im normalen Gebrauch passieren, und ich muss damit umgehen“
    • Sie helfen Ihrem Programm, unvorhersehbare Situationen zu bewältigen

Assertions sind Entwicklungs- und Debugging-Werkzeuge, die Ihnen helfen, korrekten Code zu schreiben. Exceptions sind Produktionswerkzeuge, die Ihrem Programm helfen, mit der chaotischen Realität von Benutzereingaben, Dateisystemen, Netzwerken und anderen externen Faktoren umzugehen, die Sie nicht kontrollieren können.


Sie haben jetzt die grundlegenden Debugging- und Testtechniken gelernt, die Sie auf Ihrer gesamten Programmierreise begleiten werden:

  • Tracebacks lesen, um schnell zu lokalisieren, wo Fehler auftreten
  • Code mental nachverfolgen, um Schritt für Schritt zu verstehen, was Ihr Code tut
  • Print-Statements strategisch nutzen, um Laufzeitwerte und Ablauf zu sehen
  • Objekte inspizieren mit type() und dir(), um zu verstehen, womit Sie arbeiten
  • Mit Assertions testen, um zu verifizieren, dass Ihr Code funktioniert, und Bugs früh zu finden

Diese Fähigkeiten arbeiten zusammen als ein vollständiges Debugging-Toolkit. Wenn Sie auf ein Problem stoßen:

  1. Lesen Sie den Traceback, um zu finden, wo es fehlgeschlagen ist
  2. Verwenden Sie Print-Debugging oder mentales Nachverfolgen, um zu verstehen, warum
  3. Nutzen Sie type/dir-Inspektion, wenn Sie unsicher sind, was ein Objekt kann
  4. Schreiben Sie Assertions, um zu verhindern, dass der Bug zurückkehrt

Mit Übung entwickeln Sie ein Gefühl dafür, welche Technik Sie in welcher Situation einsetzen sollten. Denken Sie daran: Jeder Programmierer debuggt Code—der Unterschied ist, dass erfahrene Programmierer es systematisch und effizient tun. Diese Techniken werden Sie zu einem von ihnen machen.

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