Автор оригинала: Steven Kryskalla.
Встроенный модуль Python pdb
чрезвычайно полезен для интерактивной отладки, но имеет небольшую кривую обучения. Долгое время я придерживался базового print
-debugging и использовал pdb
на ограниченной основе, что означало, что я упустил множество функций, которые сделали бы отладку быстрее и проще.
В этом посте я покажу вам несколько советов, которые я получил за эти годы, чтобы повысить свои навыки интерактивной отладки.
Отладка печати по сравнению с интерактивной отладкой
Во-первых, почему вы хотите использовать интерактивный отладчик вместо вставки print
или logging
операторов в свой код?
С помощью pdb
у вас гораздо больше гибкости для запуска, возобновления и изменения выполнения вашей программы, не касаясь базового источника. Как только вы научитесь этому, это означает больше времени, потраченного на погружение в проблемы, и меньше времени на переключение контекста между редактором и командной строкой.
Кроме того, не касаясь базового исходного кода, вы сможете перейти к стороннему коду (например, модулям, установленным из PyPI) и стандартной библиотеке.
Посмертная отладка
Первым рабочим процессом, который я использовал после перехода из print
debugging, был режим “посмертной отладки” pdb
. Здесь вы запускаете свою программу как обычно, но всякий раз, когда возникает необработанное исключение, вы опускаетесь в отладчик, чтобы покопаться в состоянии программы. После этого вы попытаетесь исправить ситуацию и повторите процесс до тех пор, пока проблема не будет решена.
Вы можете запустить существующий сценарий с помощью отладчика post-mortem, используя параметр Python -pdb
:
python3 -mpdb path/to/script.py
Отсюда вы попадаете в приглашение (Pdb)
. Чтобы начать выполнение, вы используете команду continue
или c
. Если программа будет выполнена успешно, вы вернетесь в приглашение (Pdb)
, где вы сможете снова перезапустить выполнение. На этом этапе вы можете использовать quit
/ q
или Ctrl+D для выхода из отладчика.
Если программа выдает необработанное исключение, вы также увидите приглашение (Pdb)
, но выполнение программы остановится в строке, которая вызвала исключение . Отсюда вы можете запустить код Python и команды отладчика в приглашении для проверки текущего состояния программы.
Тестирование нашего основного рабочего процесса
Чтобы увидеть, как работают эти основные шаги отладки, я буду использовать эту (ошибочную) программу:
import random MAX = 100 def main(num_loops=1000): for i in range(num_loops): num = random.randint(0, MAX) denom = random.randint(0, MAX) result = num / denom print("{} divided by {} is {:.2f}".format(num, denom, result)) if __name__ == "__main__": import sys arg = sys.argv[-1] if arg.isdigit(): main(arg) else: main()
Мы ожидаем, что программа выполнит некоторые основные математические операции со случайными числами в цикле и выведет результат. Попробуйте запустить его нормально, и вы увидите одну из ошибок:
$ python3 script.py 2 divided by 30 is 0.07 65 divided by 41 is 1.59 0 divided by 70 is 0.00 ... 38 divided by 26 is 1.46 Traceback (most recent call last): File "script.py", line 16, inmain() File "script.py", line 7, in main result = num / denom ZeroDivisionError: division by zero
Давайте попробуем посмертно отладить эту ошибку:
$ python3 -mpdb script.py > ./src/script.py(1)() -> import random (Pdb) c 49 divided by 46 is 1.07 ... Traceback (most recent call last): File "/usr/lib/python3.4/pdb.py", line 1661, in main pdb._runscript(mainpyfile) File "/usr/lib/python3.4/pdb.py", line 1542, in _runscript self.run(statement) File "/usr/lib/python3.4/bdb.py", line 431, in run exec(cmd, globals, locals) File " ", line 1, in File "./src/script.py", line 1, in import random File "./src/script.py", line 7, in main result = num / denom ZeroDivisionError: division by zero Uncaught exception. Entering post mortem debugging Running 'cont' or 'step' will restart the program > ./src/script.py(7)main() -> result = num / denom (Pdb) num 76 (Pdb) denom 0 (Pdb) random.randint(0, MAX) 56 (Pdb) random.randint(0, MAX) 79 (Pdb) random.randint(0, 1) 0 (Pdb) random.randint(1, 1) 1
Как только сработает посмертный отладчик, мы сможем проверить все переменные в текущем кадре и даже запустить новый код, чтобы помочь нам выяснить, что не так, и попытаться исправить ситуацию.
Переход в отладчик из кода Python с помощью pdb.set_trace
Другой метод, который я использовал на раннем этапе , после начала использования pdb
, состоял в том, чтобы заставить отладчик работать в определенной строке кода до возникновения ошибки. Это обычный следующий шаг после изучения посмертной отладки, потому что он похож на отладку с помощью операторов print
.
Например, в приведенном выше коде, если мы хотим остановить выполнение перед операцией деления, мы могли бы добавить вызов pdb.set_trace
в нашу программу здесь:
import pdb; pdb.set_trace() result = num / denom
А затем запустите нашу программу без -mpdb
:
$ python3 script.py > ./src/script.py(10)main() -> result = num / denom (Pdb) num 94 (Pdb) denom 19
Проблема с этим методом заключается в том, что вы должны постоянно помещать эти операторы в исходный код, не забудьте удалить их впоследствии и переключаться между запуском кода с помощью python
и python-mpdb
.
Использование pdb.set_trace
позволяет выполнить работу, но точки останова – это еще более гибкий способ остановить отладчик в любой строке (даже в коде сторонних разработчиков или стандартной библиотеки) без необходимости изменять исходный код. Давайте узнаем о точках останова и нескольких других полезных командах.
Команды отладчика
Существует более 30 команд, которые вы можете дать интерактивному отладчику, список которых можно просмотреть с помощью команды help
в командной строке (Pdb)
:
(Pdb) help Documented commands (type help): ======================================== EOF c d h list q rv undisplay a cl debug help ll quit s unt alias clear disable ignore longlist r source until args commands display interact n restart step up b condition down j next return tbreak w break cont enable jump p retval u whatis bt continue exit l pp run unalias where
Вы можете использовать help
для получения дополнительной информации о данной команде.
Вместо того, чтобы ходить по каждой команде, я перечислю те, которые я нашел наиболее полезными, и какие аргументы они принимают.
Установка точек останова :
l(ist)
: отображает исходный код текущей запущенной программы с номерами строк для 10 строк вокруг текущего оператора.l 1,999
: отображает исходный код строк 1-999. Я регулярно использую это, чтобы увидеть источник для всей программы. Если в вашей программе всего 20 строк, она просто покажет все 20 строк.b(точка останова)
: отображает список текущих точек останова.b 10
: установите точку останова в строке 10. Точки останова обозначаются числовым идентификатором, начинающимся с 1.b main
: установите точку останова в функции с именемmain
. Имя функции должно находиться в текущей области. Вы также можете установить точки останова для функций в других модулях текущей области, напримерb random.randint
.b script.py:10
: устанавливает точку останова в строке 10 вscript.py
. Это дает вам еще один способ установить точки останова в другом модуле.очистить
: очищает все точки останова.очистить 1
: очистить точку останова 1.
Пошаговое выполнение :
c(продолжить)
: выполнение до тех пор, пока программа не завершится, не возникнет исключение или не будет достигнута точка останова.s(tep)
: выполните следующую строку, что бы это ни было (ваш код, stdlib, сторонний код и т. Д.). Используйте это, когда вы хотите перейти к вызовам функций, которые вас интересуют.n(ext)
: выполните следующую строку в текущей функции (не будет переходить в последующие вызовы функций). Используйте это, когда вас интересует только текущая функция.r(eturn)
: выполняйте оставшиеся строки в текущей функции до тех пор, пока она не вернется. Используйте это, чтобы пропустить остальную часть функции и подняться на уровень выше. Например, если вы по ошибке перешли в функцию.unt(il) [lineno]
: выполнение до тех пор, пока текущая строка не превысит номер текущей строки. Это полезно, когда вы вступили в цикл, но хотите, чтобы цикл продолжал выполняться без необходимости вручную проходить каждую итерацию. Без каких-либо аргументов эта команда ведет себя какnext
(с поведением пропуска цикла, как только вы один раз прошли через тело цикла).
Перемещение вверх и вниз по стеку :
w(здесь)
: показывает аннотированное представление трассировки стека с текущим кадром, отмеченным символом>
.u(p)
: перемещение вверх на один кадр в текущей трассировке стека. Например, при посмертной отладке вы начнете с самого низкого уровня стека и, как правило, захотите несколько раз переместитьвверх
, чтобы выяснить, что пошло не так.d(собственный)
: перемещение вниз на один кадр в текущей трассировке стека.
Дополнительные команды и советы :
pp
: Это “довольно напечатает” результат данного выражения с помощью модуляpprint
. Пример:
(Pdb) stuff = "testing the pp command in pdb with a big list of strings" (Pdb) pp [(i, x) for (i, x) in enumerate(stuff.split())] [(0, 'testing'), (1, 'the'), (2, 'pp'), (3, 'command'), (4, 'in'), (5, 'pdb'), (6, 'with'), (7, 'a'), (8, 'big'), (9, 'list'), (10, 'of'), (11, 'strings')]
!<код python>
: иногда код Python, который вы запускаете в отладчике, будет перепутан с командой. Например,c
вызовет командуcontinue
. Чтобы заставить отладчик выполнять код Python, префикс строки!
, например!c
.Нажатие клавиши Enter в командной строке
(Pdb)
приведет к повторному выполнению предыдущей команды. Это наиболее полезно после командs
/n
/r
/unt
для быстрого пошагового выполнения построчно.Вы можете выполнить несколько команд в одной строке , разделив их с помощью
;;
, напримерb 8 ;; c
.Модуль
pdb
может принимать несколько аргументов-c
в командной строке для выполнения команд сразу после запуска отладчика.
Пример:
python3 -mpdb -cc script.py # run the program without you having to enter an initial "c" at the prompt python3 -mpdb -c "b 8" -cc script.py # sets a breakpoint on line 8 and runs the program
Поведение перезапуска
Еще одна вещь, которая может сократить время отладки, – это понимание того, как работает поведение перезапуска pdb
. Возможно, вы заметили, что после остановки выполнения pdb
выдаст сообщение типа “Программа завершена и будет перезапущена” или “Скрипт будет перезапущен.” Когда я впервые начал использовать pdb
, я всегда выходил и повторно запускал python -npdb ...
чтобы убедиться, что мои изменения в коде были приняты, что в большинстве случаев было излишним.
Когда pdb
говорит, что он перезапустит программу, или когда вы используете команду restart
, изменения кода в отлаживаемом скрипте будут автоматически перезагружены. Точки останова по-прежнему будут установлены после перезагрузки, но, возможно, потребуется очистить и сбросить их из-за смещения номеров строк. Изменения кода в другие импортированные модули не будут перезагружены — вам нужно будет выйти
и повторно запустить команду -mpdb
, чтобы забрать их.
Часы
Одна из функций, которую вы можете пропустить в других интерактивных отладчиках, – это возможность “наблюдать” за изменением переменной на протяжении всего выполнения программы. pdb
по умолчанию не включает команду watch, но вы можете получить нечто подобное с помощью commands
, которая позволяет запускать произвольный код Python при попадании в точку останова.
Чтобы посмотреть, что происходит с переменной denom
в нашем примере программы:
$ python3 -mpdb script.py > ./src/script.py(1)() -> import random (Pdb) b 9 Breakpoint 1 at ./src/script.py:9 (Pdb) commands (com) silent (com) print("DENOM: {}".format(denom)) (com) c (Pdb) c DENOM: 77 71 divided by 77 is 0.92 DENOM: 27 100 divided by 27 is 3.70 DENOM: 10 82 divided by 10 is 8.20 DENOM: 20 ...
Сначала мы устанавливаем точку останова (которой присваивается идентификатор 1), а затем используем команды
, чтобы начать ввод блока команд. Эти команды функционируют так, как если бы вы ввели их в командной строке (Pdb)
. Они могут быть либо кодом Python, либо дополнительными командами pdb
.
Как только мы запустим блок commands
, приглашение изменится на (com)
. Команда silent
означает, что следующие команды не будут воспроизводиться на экране каждый раз, когда они выполняются, что немного облегчает чтение выходных данных.
После этого мы запускаем инструкцию print
для проверки переменной, аналогично тому, что мы могли бы сделать при отладке print
. Наконец, мы заканчиваем a c
, чтобы продолжить выполнение, которое завершает командный блок. Набрав c
снова в (Pdb)
приглашение начинает выполнение, и мы видим, что наш новый print
оператор запущен.
Если вы предпочитаете остановить выполнение вместо продолжения, вы можете использовать end
вместо c
в блоке команд.
Запуск pdb из интерпретатора
Другой способ запустить pdb
– через интерпретатор, который полезен, когда вы экспериментируете в интерактивном режиме и хотели бы перейти в pdb
без запуска автономного сценария.
Для посмертной отладки все, что вам нужно,-это позвонить в pdb.pm()
после возникновения исключения:
$ python3 >>> import script >>> script.main() 17 divided by 60 is 0.28 ... 56 divided by 94 is 0.60 Traceback (most recent call last): File "", line 1, in File "./src/script.py", line 9, in main result = num / denom ZeroDivisionError: division by zero >>> import pdb >>> pdb.pm() > ./src/script.py(9)main() -> result = num / denom (Pdb) num 4 (Pdb) denom 0
Если вы хотите вместо этого перейти к обычному выполнению, используйте функцию pdb.run()
:
$ python3 >>> import script >>> import pdb >>> pdb.run("script.main()") >(1) () (Pdb) b script:6 Breakpoint 1 at ./src/script.py:6 (Pdb) c > ./src/script.py(6)main() -> for i in range(num_loops): (Pdb) n > ./src/script.py(7)main() -> num = random.randint(0, MAX) (Pdb) n > ./src/script.py(8)main() -> denom = random.randint(0, MAX) (Pdb) n > ./src/script.py(9)main() -> result = num / denom (Pdb) n > ./src/script.py(10)main() -> print("{} divided by {} is {:.2f}".format(num, denom, result)) (Pdb) n 66 divided by 70 is 0.94 > ./src/script.py(6)main() -> for i in range(num_loops):
Это немного сложнее, чем -mpdb
, потому что у вас нет возможности пройти через всю программу. Вместо этого вам нужно будет вручную установить точку останова, например, в первом операторе функции, которую вы пытаетесь выполнить.
Вывод
Надеюсь, эти советы дали вам несколько новых идей о том, как использовать pdb
более эффективно. После того, как вы справитесь с ними, вы сможете выбрать другие команды и начать настройку pdb
через файл .pdbrc
( пример ).
Вы также можете изучить другие интерфейсы для отладки, такие как pdbpp , pdb и pdb , или отладчики графического интерфейса , такие как тот, который включен в PyCharm. Счастливой отладки!