W Pythonie debugger uruchamiasz, wpisującbreakpoint()w miejscu, w którym chcesz zatrzymać program, albo startując skrypt przezpython -m pdb skrypt.py. W otwartym wierszu(Pdb)sprawdzasz zmienne poleceniemp, krokujesz przeznis, a program wznawiasz przezc. To samo zrobisz w VS Code, klikając numer linii i naciskając F5.
Zanim zaczniesz: ten artykuł zakłada, że wiesz, czym jest debugger, breakpoint i krokowanie. Jeśli nie, zacznij od wprowadzenia do debuggera. Debugger w JavaScripcie opisuje osobny artykuł o debugowaniu w DevTools i VS Code. Tutaj: Python.
Jak uruchomić debugger w Pythonie?
Masz trzy sposoby, wszystkie z wbudowanego modułu pdb:
breakpoint()w kodzie. Wpisujesz w miejscu, w którym program ma się zatrzymać, a Python (od wersji 3.7) uruchamiapdb:
def area(a, b):
c = a * b
breakpoint()
return c
python -m pdb skrypt.py. Uruchamia cały skrypt pod kontrolą debuggera, od pierwszej linii, bez zmian w kodzie. Breakpointy ustawiasz poleceniemb.- Po awarii (post-mortem). Gdy program kończy się nieprzechwyconym wyjątkiem,
python -m pdb skrypt.pywchodzi w debuggera automatycznie, w miejscu błędu.
Zatrzymany program pokazuje wiersz (Pdb), w którym wpisujesz polecenia.
Jak wyłączyć breakpoint() bez usuwania go z kodu?
Ustaw zmienną środowiskową PYTHONBREAKPOINT=0. Wszystkie wywołania breakpoint() są wtedy pomijane i program działa dalej:
PYTHONBREAKPOINT=0 python bp.py
12
To przydatne, gdy breakpoint() został w kodzie, a program musi chwilowo przejść bez zatrzymań (np. w skrypcie albo na serwerze). Nie zastępuje usunięcia go przed commitem. Ta sama zmienna pozwala podstawić inny debugger: wartość to ścieżka do funkcji, np. PYTHONBREAKPOINT=ipdb.set_trace, jeśli masz ipdb zainstalowany.
Jakie polecenia pdb warto znać? Tabela z odpowiednikami w VS Code
Pełna lista jest w dokumentacji, ale na co dzień wystarcza kilkanaście poleceń. Większość ma też skrót jednej litery:
Polecenie pdb | Co robi | Odpowiednik w VS Code |
|---|---|---|
b plik.py:10 | ustawia breakpoint w linii 10 | klik na lewo od numeru linii |
b 42, x > 10 | breakpoint warunkowy (conditional breakpoint) | PPM na breakpoincie → Edit Breakpoint → Expression |
c | wznawia do następnego breakpointu | F5 |
n | wykonuje bieżącą linię (step over) | F10 |
s | wchodzi do wywoływanej funkcji (step into) | F11 |
r | kończy bieżącą funkcję (step out) | Shift+F11 |
unt | jedzie do następnej linii większej niż bieżąca, wychodząc z pętli | podobnie działa Run to Cursor w menu kontekstowym |
p wyrażenie | wypisuje wartość | panel Variables lub Debug Console |
pp wyrażenie | wypisuje ładnie sformatowaną wartość | Debug Console |
display wyrażenie | pokazuje wartość po każdym kroku, jeśli się zmieniła | panel Watch |
l / ll | pokazuje kod wokół bieżącej linii / całą funkcję | edytor z podświetloną linią |
w | pokazuje stos wywołań (call stack) | panel Call Stack |
u / d | przechodzi w górę / w dół stosu | klik w ramkę w panelu Call Stack |
a | pokazuje argumenty bieżącej funkcji | panel Variables |
interact | otwiera interaktywną konsolę w bieżącym zakresie | Debug Console |
!instrukcja | wykonuje instrukcję Pythona w bieżącym zakresie | Debug Console |
q | kończy debugowanie (program jest przerwany) | Shift+F5 |
Skróty VS Code są domyślne i mogą się różnić zależnie od systemu i ustawień. Polecenie h w pdb wyświetla pomoc, a h polecenie opis jednego polecenia.
Jak znaleźć błąd widoczny tylko w jednej iteracji pętli? Przykład z warunkowym breakpointem
Funkcja liczy saldo konta na podstawie listy transakcji. W danych jest 200 transakcji, a wynik jest zły:
def final_balance(transactions):
balance = 0
for tx in transactions:
if tx["type"] == "deposit":
balance += tx["amount"]
elif tx["type"] == "withdrawal":
balance -= tx["amount"]
return balance
from balance import final_balance
transactions = []
for i in range(200):
transactions.append({"type": "deposit", "amount": 10})
transactions[137] = {"type": "refund", "amount": 25}
print(final_balance(transactions))
1990
Spodziewana suma to 2015 (199 wpłat po 10 i zwrot 25). Dwieście iteracji, a błąd dotyczy jednej. Breakpoint w każdej iteracji zatrzymywałby program dwieście razy, więc ustawiasz warunkowy, który zatrzyma się tylko wtedy, gdy typ transakcji jest inny niż oczekiwane:
python -m pdb run.py
(Pdb) b balance.py:4, tx["type"] not in ("deposit", "withdrawal")
Breakpoint 1 at balance.py:4
(Pdb) c
> balance.py(4)final_balance()
-> if tx["type"] == "deposit":
(Pdb) p tx
{'type': 'refund', 'amount': 25}
(Pdb) p balance
1370
Debugger zatrzymał się dokładnie na transakcji refund, a balance wynosi 1370 (137 wpłat po 10). Funkcja zna tylko deposit i withdrawal, a trzeci typ pomija bez ostrzeżenia. Poprawka może być różna (dodanie elif tx["type"] == "refund" albo rzucenie wyjątku dla nieznanego typu), ale przyczyna jest już widoczna i zapisana w jednym wywołaniu debuggera.
Jak debugować test, który się wywrócił? pytest --pdb i post-mortem
Gdy test pada z wyjątkiem, nie musisz zgadywać, w jakim stanie był program. pytest --pdb zatrzymuje się dokładnie w miejscu błędu i otwiera pdb (to właśnie debugowanie post-mortem, czyli „po awarii”). Przykładowy test:
from balance import final_balance
def test_amount_given_as_text():
transactions = [
{"type": "deposit", "amount": 100},
{"type": "withdrawal", "amount": "30"},
]
assert final_balance(transactions) == 70
pytest --pdb
...
E TypeError: unsupported operand type(s) for -=: 'int' and 'str'
balance.py:7: TypeError
>>> entering PDB >>>
> balance.py(7)final_balance()
-> balance -= tx["amount"]
(Pdb) p tx["amount"]
'30'
(Pdb) up
> test_balance.py(17)test_amount_given_as_text()
-> assert final_balance(transactions) == 70
(Pdb) p transactions
[{'type': 'deposit', 'amount': 100}, {'type': 'withdrawal', 'amount': '30'}]
pdb zaczyna w funkcji, w której padł wyjątek, więc od razu widzisz '30' jako tekst. Polecenie up przenosi Cię o poziom wyżej, do testu, który tę funkcję wywołał, i pokazuje całe dane wejściowe. Nie używaj tu w bez argumentu: pytest dokłada do stosu dziesiątki własnych ramek i wynik jest nieczytelny.
Po zakończeniu pracy w pdb wpisz q, a pytest przerwie bieg testów. Po więcej o pisaniu testów w pytest zobacz artykuł o TDD i BDD w pytest i o testowaniu bazy danych.
Co zrobić, gdy zmienna ma nazwę taką jak polecenie pdb?
Jeśli w kodzie masz zmienną o nazwie c, n, s albo p, to wpisanie jej w (Pdb) wykona polecenie debuggera zamiast pokazać wartość. W przykładzie z breakpoint() zmienna nazywa się c:
(Pdb) p c
12
(Pdb) !c
12
Polecenie p c wypisuje wartość zmiennej, a wykrzyknik na początku wymusza wykonanie linii jako kodu Pythona, a nie polecenia debuggera. Samo c bez żadnego prefiksu wznowiłoby program, zamiast pokazać wartość. Najprościej: do podglądu zawsze używaj p.
Jak debugować Pythona w VS Code?
Potrzebujesz rozszerzenia Python Debugger (ms-python.debugpy), które instaluje się razem z rozszerzeniem Python. Najszybciej: kliknij strzałkę obok przycisku uruchamiania w edytorze i wybierz Python Debugger: Debug Python File. Albo naciśnij F5 po utworzeniu konfiguracji w pliku .vscode/launch.json:
{
"version": "0.2.0",
"configurations": [
{
"name": "Python Debugger: bieżący plik",
"type": "debugpy",
"request": "launch",
"program": "${file}",
"console": "integratedTerminal",
"justMyCode": true
}
]
}
Breakpoint ustawiasz, klikając na lewo od numeru linii. Po zatrzymaniu panele Variables, Watch i Call Stack pokazują to samo, co p, display i w w pdb. W Debug Console możesz wpisywać wyrażenia, które wykonają się w kontekście zatrzymanego programu.
VS Code ma dwa rodzaje breakpointów, które warto znać. Warunkowy ma wyrażenie albo licznik trafień (hit count), np. >5 zatrzymuje dopiero po piątym trafieniu. Logpoint wypisuje komunikat w Debug Console bez zatrzymywania programu i bez dopisywania print w kodzie. Drugą drogą jest debugpy.breakpoint() wpisane w kodzie.
Skróty edytora, które przyspieszają pracę w VS Code, opisuje artykuł o skrótach w VS Code.
Dlaczego VS Code nie zatrzymuje się na breakpoincie? Lista kontrolna
Najczęstsze powody, w kolejności od najprostszych:
- Program uruchomiono bez debugowania. Przycisk uruchamiania i Debug Python File to dwie różne rzeczy. Breakpointy działają tylko w trybie debugowania.
- Linia nie jest wykonywana. Warunek
ifjest fałszywy, funkcja nigdy nie jest wywołana albo skrypt kończy się wcześniej. Ustaw breakpoint wcześniej, na linii, która na pewno się wykona, i krokuj dalej. - Wybrany jest zły interpreter Pythona. Debugger uruchamia kod innym Pythonem niż ten, z którego korzysta projekt, więc nie widzisz zależności z
.venv. Sprawdź w pasku statusu, który interpreter jest wybrany. justMyCodejest włączone. Domyślnie debugger pomija kod bibliotek i zatrzymuje się tylko w Twoim. Jeśli breakpoint jest w kodzie biblioteki, ustaw wlaunch.json"justMyCode": false.- Zmienna
PYTHONBREAKPOINT=0jest ustawiona w środowisku. Wtedy wywołaniabreakpoint()są ignorowane (patrz sekcja wyżej). - Kod działa w innym procesie. Dotyczy serwerów z automatycznym przeładowaniem i kodu uruchamianego w podprocesach. Wtedy zamiast uruchamiać program z VS Code, możesz uruchomić go samodzielnie przez
python -m debugpy --listen 5678 skrypt.pyi dołączyć się konfiguracją typuattachna porcie 5678. - Wybrano nie tę konfigurację. Sprawdź na pasku Run and Debug, która konfiguracja z
launch.jsonjest wybrana, i czy jejprogramluburlwskazują właściwy plik.
Zacznij od punktów 1–3. Rozwiązują większość przypadków.
Najczęstsze błędy przy debugowaniu Pythona
- Zostawione
breakpoint()w kodzie. Na serwerze zatrzyma proces i będzie czekał na wejście z klawiatury, którego nikt nie wpisze. qzamiastc.qkończy cały program,ctylko wznawia do następnego breakpointu.- Zgadywanie zamiast obserwacji. Zmieniasz kod „na próbę”, aż coś zadziała, zamiast sprawdzić stan w miejscu błędu.
- Szukanie błędu w testowanej funkcji, gdy dane wejściowe są złe. Polecenie
upwpytest --pdbpokaże, co faktycznie przekazał test. - Za późno postawiony breakpoint. Postawiony na linii, w której błąd już jest widoczny, pokazuje skutek, a nie przyczynę.
Podsumowanie
W Pythonie wystarczą trzy rzeczy: breakpoint() w kodzie, p do podglądu i n/s/c do krokowania. Do tego pytest --pdb, gdy test pada z wyjątkiem, i warunkowy breakpoint, gdy błąd jest w jednej iteracji ze stu. Gdy kod napisało AI, ten sam proces opisuje artykuł o debugowaniu kodu z AI. Jeśli dopiero zaczynasz, wróć do wprowadzenia do debuggera.


