Инструменты отладки — сердце любого языка программирования. Без них вам будет крайне сложно написать рабочий и чистый код. В этой статье мы познакомим вас с одним из таких инструментов — 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, на Talanto.work сейчас есть варианты для junior, middle, senior…
Рынок Python-вакансий большой, но удобным его назвать сложно. Формально вакансий много, однако в реальном поиске…
Срезы в Python — это больше, чем a[2:7]. В статье — быстрый разбор полезных приёмов:…
В этой статье мы рассмотрим возведение в степень в Python с помощью различных методов, от…
Дубликаты в списке Python — частая проблема. В статье показано, как быстро убрать повторы, в…
Услуга виртуального хостинга на SpaceWeb позволяет арендовать часть сервера (shared-сервера) и разместить на нём свой…