Отладка — одна из ключевых задач в разработке программного обеспечения. Даже опытные программисты часто сталкиваются с багами, которые иногда сложно выявить. В Python для этого есть встроенный отладчик — модульpdb (Python Debugger). Он позволяет пошагово выполнять код, исследовать значения переменных, ставить точки останова и контролировать поток выполнения программы.
Многие начинающие (и не только) разработчики прибегают к использованию print() для вывода значений переменных в разных частях кода. Этот подход может работать для простых случаев, но он быстро становится неудобным и неэффективным в сложных программах:
Засорение кода: приходится вставлять и удалять множество временных вызовов print().
Неинтерактивность: нельзя изменять переменные или выполнять код "на лету" во время паузы.
Ограниченный контекст: print() показывает значение только в одной точке, в то время как отладчик дает доступ ко всему состоянию программы.
pdb решает эти проблемы, предоставляя интерактивную среду прямо в месте "падения" или остановки вашего кода.
Стоит отметить, что у ряда IDE и текстовых редакторов (PyCharm, VS Code) есть интеграция с отладчиком - они используют возможности pdb под капотом, предоставляя графический интерфейс для отладки.
Существует несколько способов активировать отладчик.
Самый простой способ — запустить скрипт под управлением pdb прямо из терминала. Это особенно полезно, если программа падает с ошибкой, и необходимо изучить состояние программы в момент сбоя.
python3 -m pdb test.py
При выполнении этой команды pdb запустит скрипт "test.py" и остановится на первой исполняемой строке. Для примера пусть у нас есть файл "test.py" со следующим простеньким кодом:
def sum(a, b):
result = a + b
return result
print(sum(5, 6))
После запуска командой "python3 -m pdb test.py" вы увидите приглашение (Pdb), что означает, что вы находитесь в сессии отладчика.
eugene@Eugene:/workspace/python/test$ python3 -m pdb test.py > /workspace/python/test.py(1)<module>() -> def sum(a, b): (Pdb)
Для выхода из отладчика введем команду "q":
eugene@Eugene:/workspace/python/test$ python3 -m pdb test.py > /workspace/python/test.py(1)<module>() -> def sum(a, b): (Pdb) q eugene@Eugene:/workspace/python/test$
Это самый популярный способ. Вы можете жестко задать место, где выполнение программы должно приостановиться, добавив всего одну строку.
В Python 3.7 и новее для вставки точки останова применяется встроенная функция breakpoint(). Это современный и гибкий метод. Например:
def sum(a, b):
breakpoint() # <--- Программа остановится здесь
result = a + b
return result
print(sum(5, 6))
В старых версиях Python (также можно использовать и в текущих версиях Python) нужно явным образом импортировать модуль pdb и вызвать функцию pdb.set_trace():
import pdb
def sum(a, b):
pdb.set_trace() # <--- Программа остановится здесь
result = a + b
return result
print(sum(5, 6))
Вне зависимости от того, какой способ применяется - breakpoint() или вызов pdb.set_trace(), когда интерпретатор дойдет до строки с вызовом данных функций, он остановится, и вы также получите доступ к интерактивной консоли pdb:
eugene@Eugene:/workspace/python/test$ python3 test.py > /workspace/python/test/test.py(3)sum() -> result = a + b (Pdb) q
Для выхода из отладчика и программы также можно ввести команду "q".
Итак, мы можем запускать скрипт Python в отладчике. Но как управлять отладчиком? Для этого рассмотрим его основные команды:
n (next): выполняет текущую строку и переходит к следующей в той же функции. Не заходит внутрь вызываемых функций..
s (step): выполняет текущую строку и останавливается на первом возможном шаге. Заходит внутрь вызываемых функций.
c (continue): продолжает выполнение программы до следующей точки останова или до конца.
l (list): показывает исходный код вокруг текущей строки
p <expr> (print()): вычисляет и печатает значение выражения. Например: p user_id или p url.
pp <expr>: вычисляет и печатает значение выражения с использованием форматирования (pretty print).
b <номер строки> (break): устанавливает новую точку останова. Например: b 25 (на строку 25) или b my_function (на функцию my_function)..
b <файл>:<строка>: устанавливает новую точку останова в другом файле.
cl <номер>: удаляет точку останова.
a (args): показывает аргументы текущей функции
w (where): показывает стек вызовов (traceback), чтобы понять, как программа пришла в текущую точку.
q (quit): завершает сессию отладки и программу
В скобках показаны полные версии команд. То есть, к примеру, для выхода из отладчика и программы в целом можно использовать либо команду q, либо команду quit
Возьмем ранее определенный скрипт "test.py" со следующим кодом:
def sum(a, b):
result = a + b
return result
print(sum(5, 6))
И запустим его командой "python3 -m pdb test.py", после чего нам отобразится приглашение (Pdb), что означает, что мы в отладчике
eugene@Eugene:/workspace/python/test$ python3 -m pdb test.py > /workspace/python/test.py(1)<module>() -> def sum(a, b): (Pdb)
По умолчанию отображается первая строка скрипта (в нашем случае - def sum(a, b)). Для пошагового прохода по коду с заходом в функции введем команду "s" (step):
eugene@Eugene:/workspace/python/test$ python3 -m pdb test.py > /workspace/python/test.py(1)<module>() -> def sum(a, b): (Pdb) s > /workspace/python/test/test.py(5)() -> print(sum(5, 6)) (Pdb)
И мы видим, что отладчик перешел к строке 5 (строка print(sum(5, 6)))
Снова введем команду "s" (step):
eugene@Eugene:/workspace/python/test$ python3 -m pdb test.py > /workspace/python/test.py(1)<module>() -> def sum(a, b): (Pdb) s > /workspace/python/test/test.py(5)() -> print(sum(5, 6)) (Pdb) s --Call-- > /media/eugene/WD10EZEX/workspace/python/test/test.py(1)sum() -> def sum(a, b): (Pdb)
Мы видим, что отладчик перешел к выполнению функции sum. Последовательно введем пару раз команд "s":
eugene@Eugene:/workspace/python/test$ python3 -m pdb test.py > /workspace/python/test.py(1)<module>() -> def sum(a, b): (Pdb) s > /workspace/python/test/test.py(5)() -> print(sum(5, 6)) (Pdb) s --Call-- > /media/eugene/WD10EZEX/workspace/python/test/test.py(1)sum() -> def sum(a, b): (Pdb) s > /media/eugene/WD10EZEX/workspace/python/test/test.py(2)sum() -> result = a + b (Pdb) s > /media/eugene/WD10EZEX/workspace/python/test/test.py(3)sum() -> return result
Таким образом, функция выполнила сложение аргументов и поместила результат в переменную result. Здесь мы можем посмотреть значение переменной result, введя ее название:
(Pdb) result 11 (Pdb)
Мы видим, что результат равен 11.
Введем еще пару раз команду "s" для выполнения оставшегося кода в программе и завершим ее вводом команды "q":
(Pdb) s --Return-- > /workspace/python/test/test.py(3)sum()->11 -> return result (Pdb) s 11 --Return-- > /workspace/python/test/test.py(5)<module>()->None -> print(sum(5, 6)) (Pdb) q
Допустим, у нас есть функция для подсчета суммы элементов списка:
def total(numbers):
s = 0
for n in numbers:
s = s + n
return s
print(total([1, "2", 3, 4]))
Здесь возникнет ошибка при сложении числа и строки:
eugene@Eugene:/workspace/python/test$ python3 test.py
Traceback (most recent call last):
File "/workspace/python/test/test.py", line 7, in <module>
print(total([1, "2", 3, 4]))
^^^^^^^^^^^^^^^^^^^^^
File "/workspace/python/test/test.py", line 4, in total
s = s + n
~~^~~
TypeError: unsupported operand type(s) for +: 'int' and 'str'
eugene@Eugene:/workspace/python/test$
Вставим вызов breakpoint(), чтобы исследовать переменные в момент сбоя:
def total(numbers):
s = 0
for n in numbers:
s = s + n
breakpoint()
return s
print(total([1, "2", 3, 4]))
С помощью команд "n" пройдем по выполнению функции, пока не выпадет ошибка и затем введем команду "p n":
eugene@Eugene:/workspace/python/test$ python3 test.py > /workspace/python/test/test.py(3)total() -> for n in numbers: (Pdb) n > /workspace/python/test/test.py(4)total() -> s = s + n (Pdb) n TypeError: unsupported operand type(s) for +: 'int' and 'str' > /workspace/python/test/test.py(4)total() -> s = s + n (Pdb) p n '2' (Pdb)
И здесь мы видим, что на второй итерации значение "n" равно "2", но представляет строку, из-за чего и возникает ошибка.