Отладка Python-кода с помощью pdb
hard

Отладка Python-кода с помощью pdb

В сложных программах это лучше, чем print()

Важная часть программирования — отладка, когда мы ищем ошибки в коде, чтобы их исправить и программа заработала как нужно. В Python для этого чаще всего используют оператор print. Он позволяет выводить на экран значения переменных и результаты выполнения различных операций. Просто, понятно и предсказуемо. Хотя и муторно иногда.

Но есть и другие способы проверять код на ошибки: средства отладки, логи, тесты и интерактивные сессии. Сегодня расскажем о встроенном отладчике pdb и о том, чем он лучше оператора print.

Отладка с помощью print

Представим, что мы пишем код, который складывает какие-то числа:

def add_numbers(a, b):
   return a + b

def main():
   num1 = 10
   num2 = "20"
   print("num1:", num1)
   print("num2:", num2)
   result = add_numbers(num1, num2)
   print("Result:", result)

if __name__ == "__main__":
   main()

Запуск этого кода выдаст ошибку:

TypeError: unsupported operand type(s) for +: 'int' and 'str'

Чтобы отладить код, мы можем проверить тип переменных:

print("num1:", num1, type(num1))
print("num2:", num2, type(num2))

Запуск покажет, что num2 — это строка, и поэтому при попытке добавить её к целому числу возникает ошибка. Всё просто, удалось быстро разобраться.

А теперь представим, что у нас какая-то многопоточная программа, которая вычисляет факториалы нескольких чисел.

import threading

def factorial(n):
   if n == 1: 
       return 1
   else:
       return n * factorial(n - 1)

def calculate_factorial(num):
   result = factorial(num)
   print(f"Факториал числа {num} равен {result}")

def main():
   numbers = [0, 5, 7, 10, 12]
   threads = []

   for number in numbers:
       thread = threading.Thread(target=calculate_factorial, args=(number,))
       threads.append(thread)
       thread.start()

   for thread in threads:
       thread.join()

if __name__ == "__main__":
   main()

При запуске этого кода мы получим множество сообщений об ошибках. Что именно пошло не так, в каком потоке ошибка, что конкретно случилось и куда копать — непонятно:

Отладка Python-кода с помощью pdb

Попробуем добавить оператор print, чтобы отследить поток выполнения и определить причину ошибок:

import threading

def factorial(n):
   print(f"Вычисляем факториал для {n}")
   if n == 1:
       print(f"Достигнут базовый случай для {n}")
       return 1
   else:
       result = n * factorial(n - 1)
       print(f"Факториал для {n} после рекурсии равен {result}") 
       return result

def calculate_factorial(num):
   print(f"Начинается вычисление для {num}")
   result = factorial(num)
   print(f"Факториал {num} равен {result}")

def main():
   numbers = [0, 5, 7, 10, 12]
   threads = []

   for number in numbers:
       print(f"Создание потока для числа {number}") 
       thread = threading.Thread(target=calculate_factorial, args=(number,))
       threads.append(thread)
       thread.start()

   for thread in threads:
       thread.join()

if __name__ == "__main__":
   main()

При запуске видим такие результаты вычислений:

Отладка Python-кода с помощью pdb

Видно, что при вычислении факториала функция продолжает вызывать себя, пока не достигнет отрицательных чисел. Это приводит к ошибке рекурсии, поскольку базовый случай if n == 1 никогда не достигается. Поняв это, мы выясним, в чём ошибка и как её исправить: нужно заменить n == 1 на n == 0.

У нас получилось разобраться с помощью print, но это не очень удобно:

  • Вывод консоли перегружен операторами отладки, что затрудняет отслеживание хода выполнения программы.
  • Если программа многопоточная, операторы print могут чередоваться, а это приводит к беспорядочному выводу, который трудно читать.
  • Операторы print позволяют увидеть состояние программы только в тех местах, где они вызваны, и не всегда помогают разобраться в сложных проблемах. Например, в состоянии гонки, когда несколько потоков или процессов пытаются одновременно изменить данные.

Что такое pdb

Название pdb образуется от сокращения Python Debugger. Это встроенный отладчик Python, который позволяет выполнять код пошагово, отслеживать значения переменных, устанавливать точки останова и изучать состояние программы на разных этапах её выполнения.

Отладчик pdb помогает найти и исправить ошибки в коде и при этом даёт более глубокий контроль и понимание работы программы, чем простые операторы print.

Использовать pdb можно как непосредственно в коде, подключив к нему отладчик командой import pdb, так и при запуске скрипта из командной строки. Например, у нас есть скрипт my_script.py. Для его запуска с pdb нужно написать в терминале такую команду:

python -m pdb my_script.py

Если нам нужно проанализировать текущее состояние программы, можно указать точку останова с помощью команды функции pdb.set_trace():

import pdb; pdb.set_trace()
x = 5
y = 10
result = x + y
print(result)

Основные команды pdb:

  • n — выполнить следующую строку кода;
  • s — войти в функцию, если на этой строке есть вызов функции;
  • c — продолжить выполнение программы до следующей точки останова или конца;
  • p — показать значение переменной;
  • l — показать текущую строку кода и контекст вокруг неё;
  • w — показать текущее местоположение в стеке вызовов;
  • q — завершить сеанс отладки и выйти из программы.

Используем pdb для отладки

Вернёмся к нашему примеру программы, который вычисляет факториал. Немного изменим код, добавив отладчик pdb:

import threading
import pdb

def factorial(n):
   # устанавливаем точку останова
   pdb.set_trace()
   if n == 1: 
       return 1
   else:
       return n * factorial(n - 1)

def calculate_factorial(num):
   result = factorial(num)
   print(f"Факториал числа {num} равен {result}")

def main():
   numbers = [0, 5]
   threads = []

   for number in numbers:
       thread = threading.Thread(target=calculate_factorial, args=(number,))
       threads.append(thread)
       thread.start()

   for thread in threads:
       thread.join()

if __name__ == "__main__":
   main() 

Запустив этот код, прогнав несколько раз итерации в рекурсивной функции командой n и посмотрев на значения переменной командой p, мы видим, что pdb сразу указывает на причину ошибки:

Используем pdb для отладки

Теперь сразу видно, что значение n становится ниже 0 из-за неправильного базового случая if n == 1 вместо if n == 0. И нам не пришлось долго разбираться в результатах вывода, чтобы это понять.

Любите отладчики.

Код:

Колаволе Мангабо

Обложка:

Алексей Сухов

Корректор:

Ирина Михеева

Вёрстка:

Маша Климентьева

Соцсети:

Юлия Зубарева

Получите ИТ-профессию
В «Яндекс Практикуме» можно стать разработчиком, тестировщиком, аналитиком и менеджером цифровых продуктов. Первая часть обучения всегда бесплатная, чтобы попробовать и найти то, что вам по душе. Дальше — программы трудоустройства.
Получите ИТ-профессию Получите ИТ-профессию Получите ИТ-профессию Получите ИТ-профессию
Еще по теме
hard