Инструменты отладки — сердце любого языка программирования. Без них вам будет крайне сложно написать рабочий и чистый код. В этой статье мы познакомим вас с одним из таких инструментов — Python-отладчиком pdb (англ. Python Debugger (pdb)).
Обратите внимание: перед вами руководство по отладке кода. Мы исходим из того, что вы знакомы с Python и имеете представление о написании тест-кейсов.
От редакции Pythonist. Также предлагаем почитать о таком средстве дебага, как модуль icecream — «Забудьте об использовании print() для отладки в Python».
Вызвать pdb можно двумя способами.
Находясь в терминале, вы можете вызвать pdb при запуске на выполнение вашего файла с расширением .py.
python -m pdb <test-file-name>.py
Если вы используете poetry и pytest, вы можете вызвать отладчик pdb, добавив в конце флаг --pdb
.
poetry run python <path/to_your/test_file.py> --pdb
Для вызова pdb при помощи Docker, poetry и pytest можно использовать следующий синтаксис:
COMPOSE_PROJECT_NAME=<test_docker_image_name> docker-compose run --rm workers poetry run pytest <path/to_your/test_file.py>::<name_of_the_test_function> --pdb
Флаг --pdb
всегда добавляется после имени тестового файла. Когда тест прервется, откроется консоль pdb. Но помните, что --pdb
— это флаг pytest.
Бывает, что вы получаете ложнопозитивный результат теста. Ваш тест-кейс может пройти, но вы не получаете ожидаемых данных.
Что, если вы хотите прочесть необработанный запрос к базе данных? В этом случае вы можете вызвать отладчик pdb из функции Python.
Чтобы войти в отладчик pdb, нужно вызвать import pdb; pdb.set_trace()
внутри вашей функции.
Чтобы в этом разобраться, давайте рассмотрим пример со вложенной функцией:
# file1.py from . import function3 def function1(): // logic for function1 function3()
# file2.py def function2(): // logic for function2 // some database query
# file3.py from . import function2 def function3(): // logic for function3 function2() // logic for function3 continues
В этом примере одна функция вызывает другую.
Вы хотите добавить брейкпоинт в функцию function2
, чтобы посмотреть, что в ней происходит.
Добавить брейкпоинт можно при помощи следующей инструкции:
import pdb; pdb.set_trace()
# file2.py 1. def function2(): 2. // logic for function2 3. // line 1 4. // line 2 5. 6. import pdb; pdb.set_trace(); 7. 8. // some database query 9. // line 3 10. // line 4
Когда код ломается, pdb открывает свою консоль. Примерно так:
(Pdb)
Выполняя line2
, интерпретатор Python прочтет брейкпоинт и откроет консоль pdb. После этого вы сможете перемещаться по коду при помощи команд pdb.
Отладчик pdb — это интерактивная консольная утилита. Не познакомившись с его командами, вы не сможете использовать весь его потенциал.
Как и любой console log, pdb скажет вам, на какой строчке ваш код сломался.
Допустим, в вашем тест-кейсе есть инструкция assert
.
# test.py def test1(): ... result = function1() assert result.json = {'status_code':1, 'status': 'saved', 'description':'data saved'}
Для вывода значения в консоль используется команда p
.
(Pdb) p result.json {'status_code':1, 'status': 'saved', 'description':'data saved'}
Таким образом будет выведено значение переменной.
Команда up
перемещает вас на один шаг выше по стеку.
В случае вложенных вызовов функции эта команда переместит вас в функцию, которая вызвала вашу.
Пример:
# test.py def function1(): print("invoking function1") import pdb;pdb.set_trace() print("function1 invoked") def function2(): print("invoking function2") function1() print("function2 invoked") def function3(): print("inside function3") function2() print("function3 invoked") # starting the call with function2() function3()
В pdb вызов происходит так:
$ python -m pdb test.py > test.py(1)<module>() -> def function1(): (Pdb) n > test.py(7)<module>() -> def function2(): (Pdb) n > test.py(13)<module>() -> def function3(): (Pdb) n > test.py(20)<module>() -> function3() (Pdb) n inside function3 invoking function2 invoking function1 > test.py(4)function1() -> print("function1 invoked") (Pdb) n function1 invoked --Return-- > test.py(4)function1()->None -> print("function1 invoked") (Pdb) u > test.py(9)function2() -> function1() (Pdb) l 4 print("function1 invoked") 5 6 7 def function2(): 8 print("invoking function2") 9 -> function1() 10 print("function2 invoked") 11 12 13 def function3(): 14 print("inside function3") (Pdb) u > test.py(15)function3() -> function2() (Pdb) l 10 print("function2 invoked") 11 12 13 def function3(): 14 print("inside function3") 15 -> function2() 16 print("function3 invoked") 17 18 # starting the call with function2() 19 20 function3() (Pdb) u > test.py(20)<module>() -> function3() (Pdb) l 15 function2() 16 print("function3 invoked") 17 18 # starting the call with function2() 19 20 -> function3() [EOF] (Pdb) u > <string>(1)<module>()
Здесь мы начинаем с вызова function3()
. Выполнение останавливается, когда доходит до import pdb
.
pdb открывает консоль и ожидает инпут. Мы вводим u
для перемещения вверх, и pdb возвращает вызываемую функцию — function2()
. Еще один ввод команды u
вернет function3
(функцию, которая вызывает function2
).
Мы также используем команду list
(l
). Она выводит местонахождение строки, которая выполняется в настоящее время.
Чтобы понять команду step
, давайте продолжим разбирать предыдущий пример.
# test.py 1. def test1(): 2. ... 3. result = function1() 4. assert result.json.status_code == 1 5. assert result.json.status == 'saved' 6. assert result.json.description == 'data saved' 7.
# function_file.py def function1(): foo = ['bar'] ...
Вы подозреваете, что результат, который вернула function1()
, неправильный. Ваш код ломается на строке 6. Как вам переместиться на строку 3?
Сначала нужно использовать команду up
, а в конце войти в функцию при помощи команды s
.
(Pdb) u assert result.json.status == 'saved' (Pdb) u assert result.json.status_code == 1 (Pdb) p assert result.json.status_code 1 (Pdb) u result = function1() (Pdb) s (Pdb) n foo = ['bar']
При входе в function1()
консоль pdb начнет выводить предложения из этой функции.
Отладчик pdb — мощный инструмент. В этом руководстве мы затронули лишь основы работы с ним. Обязательно почитайте документацию, чтобы познакомиться со всеми его возможностями.
Перевод статьи «How to Debug Your Python Code with the Python Debugger (pdb)».
Управление памятью - важный, но часто упускаемый из виду аспект программирования. При неправильном подходе оно…
Как возникает круговой импорт? Эта ошибка импорта обычно возникает, когда два или более модуля, зависящих…
Вы когда-нибудь оказывались в ситуации, когда скрипт на Python выполняется очень долго и вы задаетесь…
В этом руководстве мы разберем все, что нужно знать о символах перехода на новую строку…
Блок if __name__ == "__main__" в Python позволяет определить код, который будет выполняться только при…
Давайте разберем, как настроить модульные тесты для экземпляров классов. Мы напишем тесты для проверки функциональности…