Der Python-Debugger in VS Code spart vor allem dann Zeit, wenn ein Fehler nicht sofort sichtbar ist: Statt blind mit `print` zu arbeiten, halte ich an gezielten Stellen an, prüfe Variablen und gehe den Ablauf Schritt für Schritt durch. In diesem Artikel zeige ich, wie die integrierten Debug-Funktionen praktisch funktionieren, welche `launch.json`-Einstellungen wirklich zählen und wie sich lokale, Test- und Remote-Szenarien sauber voneinander trennen lassen. Wer Python in Visual Studio Code produktiv nutzt, gewinnt damit nicht nur Tempo, sondern auch deutlich bessere Kontrolle über das eigene Programm.
Die wichtigsten Punkte auf einen Blick
- Für einfache Skripte reicht oft F5; für wiederholbare Setups ist eine eigene launch.json die bessere Wahl.
- Breakpoints, Conditional Breakpoints und Logpoints decken die meisten Debug-Fälle ohne Umwege ab.
- launch startet dein Programm direkt, attach verbindet sich mit einem bereits laufenden Prozess.
- justMyCode: false ist hilfreich, wenn du in die Standardbibliothek hineinsehen willst.
- Tests, Flask, Django und Remote-Setups funktionieren mit eigenen Profilen deutlich stabiler als mit einem generischen Start.
Warum der Python-Debugger in VS Code im Alltag mehr bringt als `print`
Wenn ich Fehler in Python analysiere, will ich nicht nur sehen, dass etwas schiefgeht, sondern wo sich der Zustand verändert. Genau das leistet der Debugger: Er stoppt die Ausführung an einer definierten Zeile, zeigt mir Variablen im aktuellen Stackframe und erlaubt es mir, den Code kontrolliert weiterlaufen zu lassen. Das ist vor allem bei Schleifen, verschachtelten Funktionsaufrufen, Zustandswechseln und Nebenwirkungen in Datenstrukturen deutlich präziser als eine Kette aus Ausgaben im Terminal.
VS Code bringt dafür die typischen Werkzeuge schon mit: Haltepunkte in der Editorleiste, die Debug-Konsole, Schritt-für-Schritt-Navigation und die Anzeige des aktuellen Aufrufstapels. Für einfache Dateien kann der Editor den aktiven Code oft direkt starten. Sobald ein Projekt aber mehr als ein Einstiegsskript, Umgebungsvariablen oder unterschiedliche Startwege hat, lohnt sich eine feste Konfiguration. Dann ist das Debugging nicht nur bequemer, sondern auch reproduzierbar.
Bevor ich tiefer einsteige, prüfe ich deshalb immer zwei Dinge: den gewählten Interpreter und die Frage, ob ich nur meinen eigenen Code oder auch Bibliothekscode untersuchen will. Damit sind schon viele Fehlstarts vermieden. Als Nächstes geht es deshalb um die saubere Einrichtung, denn dort entscheidet sich, ob Debugging später reibungslos wirkt oder jedes Mal neu zusammengesucht werden muss.

So richte ich eine saubere Debug-Konfiguration ein
Die Grundlage ist fast immer eine launch.json im Ordner .vscode. Ich öffne dafür in VS Code die Run-and-Debug-Ansicht, lege eine Konfiguration an und wähle für Python die passende Debug-Variante. Für ein einzelnes Skript reicht oft program mit ${file}; für ein Projekt mit festem Einstiegspunkt setze ich lieber direkt den Startpfad oder arbeite mit module, wenn das Programm wie bei python -m ... gestartet werden soll.
{
"name": "Python Debugger: Aktuelle Datei",
"type": "debugpy",
"request": "launch",
"program": "${file}",
"console": "integratedTerminal"
}| Feld | Wofür ich es nutze | Praktischer Effekt |
|---|---|---|
request |
launch oder attach
|
Bestimmt, ob VS Code das Programm startet oder sich an einen laufenden Prozess hängt. |
program |
Pfad zur Einstiegsskript-Datei | Gut für feste Startpunkte und reproduzierbare Sessions. |
module |
Start über Modullogik | Passend für Aufrufe, die du sonst mit python -m ... startest. |
console |
integratedTerminal oder externalTerminal
|
Steuert, wo Ausgabe und Eingaben landen; das integrierte Terminal ist meist die angenehmste Standardwahl. |
cwd |
Arbeitsverzeichnis | Wird wichtig, sobald dein Code relative Pfade zu Dateien verwendet. |
args |
Argumente für dein Programm | Hilft bei Startparametern, die du nicht jedes Mal neu eintippen willst. |
justMyCode |
Debugging auf eigenen Code begrenzen |
false ist nützlich, wenn du in die Standardbibliothek hineinsehen willst. |
subProcess |
Unterprozesse mitdebuggen | Sinnvoll bei Workern, Multiprocessing oder Prozessen, die weitere Python-Instanzen starten. |
Ich gehe in Projekten oft so vor: erst den richtigen Interpreter auswählen, dann eine schlanke Konfiguration anlegen, dann einen ersten Haltepunkt setzen und mit F5 starten. Für komplexere Fälle ist der Unterschied zwischen launch und attach entscheidend, aber die Grundidee bleibt gleich: Die Konfiguration soll den Startweg dokumentieren, nicht nur den Debugger zum Laufen bringen. Wenn das sauber sitzt, wird die eigentliche Fehlersuche deutlich schneller.
Breakpoints, Logpoints und Bedingungen sinnvoll einsetzen
Ein normaler Breakpoint ist der schnellste Weg, um an einer verdächtigen Zeile anzuhalten. In VS Code setze ich ihn direkt im Rand neben der Zeilennummer. Das reicht in vielen Fällen schon aus. Sobald aber eine Schleife hundertmal läuft oder ein Fehler nur bei einem bestimmten Zustand auftritt, wird ein einfacher Haltepunkt zu grob. Dann kommen bedingte Breakpoints ins Spiel, und genau dort wird Debugging oft erst wirklich effizient.
| Typ | Wann ich ihn nutze | Was er bringt | Worauf ich achte |
|---|---|---|---|
| Normaler Breakpoint | Bei der ersten unklaren Stelle im Code | Stoppt die Ausführung an der markierten Zeile | Er ist schnell gesetzt, kann aber bei vielen Durchläufen zu oft stoppen. |
| Conditional Breakpoint | Wenn nur ein bestimmter Fall interessiert | Stoppt nur bei einer Bedingung oder nach einer bestimmten Trefferzahl | Ideal für Schleifen, Grenzwerte und schwer reproduzierbare Fehler. |
| Logpoint | Wenn ich Informationen brauche, aber nicht anhalten will | Schreibt eine Nachricht in die Debug-Konsole | Praktisch, wenn ich den Lauf nicht unterbrechen möchte. |
Logpoints sind für mich die unterschätzte Zwischenlösung: Ich möchte den Ablauf beobachten, aber nicht bei jedem Durchlauf stoppen. Das spart Zeit, wenn ich zum Beispiel in einer Schleife nur wissen will, wie sich eine Variable entwickelt. Bedingte Breakpoints sind dagegen dann stärker, wenn ich weiß, dass der Fehler nur bei einem bestimmten Wert, einer Trefferzahl oder einem späteren Zustand auftaucht. Ein klassisches Beispiel ist eine Schleife, die erst nach dem fünften oder zwanzigsten Durchlauf kippt.
Für solche Fälle setze ich Bedingungen lieber gezielt als nach Gefühl. Das Debugging wird dadurch weniger laut und deutlich lesbarer. Im nächsten Schritt lohnt sich die Unterscheidung zwischen einem Programmstart aus VS Code und dem Andocken an einen bereits laufenden Prozess, denn genau dort unterscheiden sich viele reale Szenarien.
Launch, Attach und Remote-Debugging sauber trennen
Die wichtigste praktische Trennung ist aus meiner Sicht diese: launch startet das Programm aus VS Code heraus, attach verbindet den Debugger mit einem Prozess, der schon läuft. Für lokale Skripte ist launch meist die einfachere Wahl. Sobald ich aber auf einen Server, einen Container oder einen extern gestarteten Prozess zugreifen will, ist attach die richtige Richtung. Das verhindert, dass ich Startlogik und Debug-Logik unnötig vermische.
| Modus | Wann ich ihn bevorzuge | Stärke | Einschränkung |
|---|---|---|---|
launch |
Beim normalen Entwickeln auf dem lokalen Rechner | VS Code kontrolliert Start, Argumente und Umgebung | Der Prozess muss durch die Konfiguration reproduzierbar startbar sein. |
attach |
Wenn das Programm bereits läuft oder extern gestartet wird | Gut für Remote-Systeme, Container und Spezialfälle | Die Verbindung muss vorher vorbereitet werden, oft mit Port und Pfaden. |
Beim Remote-Debugging ist die eigentliche Ursache für Frust oft nicht der Debugger selbst, sondern die Verbindung dazwischen. Typisch ist ein Aufbau mit debugpy.listen(...) und debugpy.wait_for_client() oder ein Start über python -m debugpy --listen ... --wait-for-client. In solchen Setups braucht VS Code zusätzlich eine saubere Pfadzuordnung, also pathMappings, damit lokale Dateien und entfernte Dateien zusammenpassen. Ohne diese Zuordnung stoppt der Debugger vielleicht korrekt, aber die Zuordnung der Zeilen wirkt seltsam oder bricht an der falschen Stelle ab.
Für SSH-Tunnel ist das Prinzip ähnlich: Der entfernte Prozess hört auf einem Port, lokal leite ich denselben Port durch den Tunnel weiter, und VS Code verbindet sich dann auf localhost. Das ist deutlich sicherer als offene Ports und in der Praxis auch besser wartbar. Sobald diese Trennung sitzt, wird die letzte Hürde meist nicht mehr die Verbindung, sondern der spezielle Anwendungsfall selbst.
Tests, Flask und Django profitieren von eigenen Profilen
Ein generisches Debug-Profil reicht bei Tests oder Web-Frameworks oft nur für den ersten Versuch. Danach wird es wertvoll, die Konfiguration auf den Einsatzzweck zuzuschneiden. Für Testläufe kann VS Code direkt einzelne Tests, alle Tests in einer Datei oder den Test an der Cursorposition debuggen. Ich nutze das gern, wenn ein Fehler nur in einem bestimmten Testfall auftritt und ich nicht das ganze Projekt mitlaufen lassen will.
| Szenario | Typische Konfiguration | Warum das hilft |
|---|---|---|
| Tests | purpose: ["debug-test"] |
Trennt Test-Debugging sauber vom normalen Programmstart und passt zur Test-Ansicht in VS Code. |
| Flask |
module: "flask", env mit FLASK_APP, args: ["run", "--no-debugger"]
|
Entspricht dem echten Startweg der Anwendung und vermeidet falsche Annahmen über den Einstiegspunkt. |
| Django |
program: "${workspaceFolder}/manage.py", args: ["runserver"], django: true
|
Aktiviert die für Django relevanten Debug-Funktionen und die Template-Unterstützung. |
Ein Detail wird dabei oft übersehen: Testdateien werden beim Debuggen nicht automatisch gespeichert. Ich prüfe also vor dem Start immer, ob der aktuelle Stand wirklich auf der Festplatte liegt. Sonst analysiert man schnell eine alte Version und sucht an der falschen Stelle. Bei Tests kann außerdem justMyCode: false sinnvoll sein, wenn der Fehler tiefer in der Standardbibliothek oder im Laufzeitverhalten sichtbar wird.
Für Web-Apps ist die eigentliche Lehre dieselbe: Nicht jeder Startweg passt zu jedem Projekt. Je näher die Debug-Konfiguration am realen Start liegt, desto weniger Überraschungen gibt es später. Wenn trotzdem etwas nicht stoppt, bleiben meist nur ein paar wiederkehrende Ursachen übrig, die ich als Erstes ausschließe.
Wenn der Debugger nicht stoppt, prüfe ich zuerst diese Fehlerquellen
Die meisten Probleme beim Debuggen sind banal, aber genau deshalb zeitfressend. Ich beginne dann nicht mit der komplizierten Theorie, sondern mit den Basics: Ist die Python-Debugger-Erweiterung installiert und aktiviert? Ist der richtige Interpreter ausgewählt? Ist der Breakpoint überhaupt auf einer ausführbaren Zeile gesetzt? Gerade bei wechselnden virtuellen Umgebungen ist der falsche Interpreter einer der häufigsten Gründe dafür, dass sich das Verhalten nicht so anfühlt wie erwartet.
- Erweiterung prüfen: Ohne aktivierte Python-Debugger-Erweiterung startet die Session nicht wie gedacht.
- Interpreter abgleichen: Der Debugger nutzt standardmäßig den für den Workspace gewählten Interpreter.
-
Typ kontrollieren: In modernen Setups sollte
typeaufdebugpystehen, nicht auf den alten Wertpython. - Watch-Ausdrücke bereinigen: Ungültige Ausdrücke in der Watch-Ansicht können eine Session unnötig stören.
- Breakpoints validieren: Nicht ausführbare Zeilen werden vom Debugger oft auf die nächste sinnvolle Stelle verschoben.
-
Thread-Sonderfälle beachten: Bei nativen Threads kann
debugpy.debug_this_thread()nötig sein.
Bei Attach-Problemen auf Linux kann außerdem die Systemkonfiguration rund um ptrace_scope eine Rolle spielen. Das ist kein typischer Anwendungsfehler, aber genau deshalb wird es gerne übersehen, wenn der Debugger beim Anhängen an einen laufenden Prozess einfach hängen bleibt oder mit Timeout abbricht. Ich halte solche Systemursachen erst dann für relevant, wenn die Grundkonfiguration schon sauber aussieht.
Oft ist die richtige Reihenfolge also wichtiger als die nächste zusätzliche Option: Erst Startweg, dann Interpreter, dann Breakpoint, dann Spezialfall. Genau so bleibt Debugging beherrschbar und wird nicht zur Suche nach dem einen geheimen Schalter.
Was in der Praxis den größten Unterschied macht
Am Ende ist Debugging in VS Code dann am stärksten, wenn es kurz, reproduzierbar und sichtbar bleibt. Ich will einen klaren Startpunkt, einen passenden Haltepunkt und einen Weg, Variablen ohne Rätselraten zu prüfen. Sobald das Projekt größer wird, sind saubere Konfigurationen und passende Debug-Profile nicht mehr Komfort, sondern Teil der Arbeitsmethode.
Wenn ich ein Python-Projekt aufräume, halte ich deshalb immer drei Dinge fest: den korrekten Interpreter, eine kleine, eindeutige launch.json und die Frage, ob ich wirklich launch brauche oder doch attach. Genau dort geht sonst die meiste Zeit verloren. Wer diese Grundlagen einmal sauber eingerichtet hat, debuggt ruhiger, schneller und deutlich nachvollziehbarer.
Für den Alltag reicht oft schon diese Haltung: nicht mehr Optionen sammeln, sondern die wenigen wirklich relevanten sauber beherrschen.