Skip to content

Краткая инструкция по эффективному использованию pdb

License

Notifications You must be signed in to change notification settings

0xfadeef/pdb-tutorial

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

27 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

pdb: Инструкция к применению

Цель данного руководства – научить вас основам работы с pdb, Python DeBugger для Python2 и Python3. Также вы найдёте здесь несколько полезных приёмов, которые значительно облегчат для вас процесс отладки кода.


Переводы

Оригинал этой инструкции написан на английском, однако, при поддержке участников сообщества Python появились переводы и на другие языки:

Если вы хотите видеть здесь больше переводов или готовы предложить свою помощь в подготовке перевода, оставьте комментарий здесь.


Всюду далее предполагается, что вы используете Python 2.7 или Python 3.4. Я буду подчёркивать отличия между версиями, если это влияет на формат команд pdb. Для того, чтобы узнать вашу версию Python, введите в терминале следующую команду:

python --version

Теперь, когда вы знаете версию, мы можем приступать.

Для чего нужен отладчик?

Вначале давайте немного поговорим о том, почему отладка кода и использование инструментов отладки играют важную роль в разработке ПО. Здесь я могу выделить три наиболее важных на мой взгляд функции отладчика.

С помощью отладчика вы можете:

  • Исследовать состояние программы в ходе её выполнения
  • Протестировать код перед тем, как применить изменения
  • Пошагово следовать логике исполнения программы

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

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

Потребуется некоторое время, прежде чем вы освоитесь и начнёте чувствовать себя уверенно при работе в окружении отладчика. Цель этого руководства – дать вам возможность "прощупать почву" перед тем, как вы начнёте применять pdb при работе со своим кодом.

Сыграем в игру

Итак, мы поговорили о назначении отладчика и теперь пришло время увидеть его в действии. Прежде всего вам необходимо будет склонировать данный репозиторий, если вы ещё этого не сделали. Если у вас не установлен git (или любая другая система контроля версий), я рекомендую сделать это, инструкции по установке git можно найти здесь. После установки git, склонируйте репозиторий, выполнив в терминале следующую команду:

git clone https://github.com/spiside/pdb-tutorial

NB: Если вы столкнулись с трудностями на данном этапе, следуйте инструкции Github как клонировать репозиторий.

Теперь в локальной копии репозитория перейдите в корень проекта и откройте файл с заданием:

cd /path/to/pdb-tutorial

file: instructions.txt

Ваш босс поручил вам исправить ошибку в проекте. Проект представляет собой простую игру, в которой компьютер
моделирует бросание игральной кости. Цель игры – правильно сложить числа, выпавшие на костях в серии из 6-и
последовательных выбрасываний.

Проблема заключается в том, что программист, работавший над игрой, не умел пользоваться отладчиком.
Теперь ваша задача - исправить все недочёты и представить рабочий вариант игры.

Чтобы начать игру, нужно запустить файл main.py.

Выглядит довольно просто! Для начала давайте сыграем в эту игру и попробуем выяснить, что с ней не так. Чтобы запустить программу, введите в терминале:

python main.py

На экране вы увидите что-то вроде такого:

Add the values of the dice
It's really that easy
What are you doing with your life.
Round 1

---------
|*      |
|       |
|      *|
---------
---------
|*      |
|       |
|      *|
---------
---------
|*     *|
|       |
|*     *|
---------
---------
|*     *|
|       |
|*     *|
---------
---------
|*     *|
|   *   |
|*     *|
---------
Sigh. What is your guess?: 

Похоже, ваш предшественник обладал... своеобразным чувством юмора. Как бы то ни было, вводим "17" (так как это – суммарное значение очков, выпавших на костях).

Sigh. What is your guess?: 17
Sorry that's wrong
The answer is: 5
Like seriously, how could you mess that up
Wins: 0 Loses 1
Would you like to play again?[Y/n]: 

Странно. Программа пишет, что ответ – 5, но это, очевидно, неверно... Возможно, сложение очков реализовано некорректно. Давайте сыграем ещё раз и проверим это. Следуя подсказке на экране, введём 'Y', чтобы начать игру заново.

Would you like to play again?[Y/n]: Y
Traceback (most recent call last):
  File "main.py", line 12, in <module>
    main()
  File "main.py", line 8, in main
    GameRunner.run()
  File "/Users/Development/pdb-tutorial/dicegame/runner.py", line 62, in run
    i_just_throw_an_exception()
  File "/Users/Development/pdb-tutorial/dicegame/utils.py", line 13, in i_just_throw_an_exception
    raise UnnecessaryError("You actually called this function...")
dicegame.utils.UnnecessaryError: You actually called this function...

Да, что-то определённо пошло не так. Программа должна была нормально обработать введённый символ, но вместо этого выбросила исключение. Думаю, теперь можно с уверенностью сказать, что в коде имеются ошибки, так что давайте запустим отладчик и попробуем исправить положение!

PDB 101: Введение в pdb

Пришло время наконец начать работу с собственным отладчиком Python – pdb. Отладчик является частью стандартной библиотеки Python и мы можем использовать его точно так же, как и любую другую библиотеку языка. Сначала мы должны импортировать модуль pdb, а затем вызвать один из его методов, чтобы добавить в программу точку останова. Следуя устоявшейся практике, импорт и вызов метода размещаются на одной строке, в том месте, где вы хотели бы прервать выполнение программы. Конструкция, которую необходимо добавить в код, выглядит так:

import pdb; pdb.set_trace()

Начиная с версии Python 3.7, доступна встроенная функция breakpoint(), которая является более удобной заменой для import pdb; pdb.set_trace()

breakpoint()

Оба метода, set_trace() и breakpoint(), устанавливают точку останова в той строке, где вызывается метод. Далее в примерах мы будем использовать метод set_trace(), так как он более универсален и поддерживается во всех версиях языка. Откроем файл main.py и добавим точку останова на строку 8:

file: main.py

from dicegame.runner import GameRunner


def main():
    print("Add the values of the dice")
    print("It's really that easy")
    print("What are you doing with your life.")
    import pdb; pdb.set_trace() # add pdb here
    GameRunner.run()


if __name__ == "__main__":
    main()

Запустим main.py снова и посмотрим, что произойдёт.

python main.py
Add the values of the dice
It's really that easy
What are you doing with your life.
> /Users/Development/pdb-tutorial/main.py(9)main()
-> GameRunner.run()
(Pdb)

Бинго! Мы "поставили на паузу" выполнение программы и теперь можем осмотреться вокруг. Я думаю, что первая проблема, которой стоит заняться, это неверное сложение значений выпадающих на костях.

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

5 команд pdb, которые лишат вас дара речи

Представляю вашему вниманию пять команд pdb, освоив которые, вы уже не будете представлять, как обходились без них раньше.

  1. l(ist) – Вывести на экран 11 строчек кода (по 5 строчек до и после текущей строки) или продолжить предыдущий вывод.
  2. s(tep) – Выполнить текущую строку, остановиться на следующей строке по ходу выполнения.
  3. n(ext) – Продолжить выполнение до следующей строки исполняемой функции или возврата из неё.
  4. b(reak) – Добавить точку останова (поведение зависит от передаваемых аргументов).
  5. r(eturn) – Продолжить выполнение до возврата из исполняемой функции.

Обратите внимание на скобки вокруг окончания каждого ключевого слова. Скобки означают, что в командной строке pdb данную часть слова набирать не обязательно. Это сэконмит вам нажатия клавиш, но фокус в том, что если в вашем коде есть переменные, названные, например, l или n, команды pdb всегда будут иметь приоритет. Предположим, что где-то в вашей программе объявлена переменная c и вы хотите узнать её значение. Если вы наберёте c в командной строке, pdb интерпретирует это как команду c(ontinue) и, вместо вывода значения переменной, продолжит выполнение программы до следующей точки останова.

NB: Я, как и многие другие программисты, не одобряю использование коротких имён переменных, таких как a, b, gme и т. п. Такие названия не несут смысла и затрудняют чтение кода для других людей. В примере выше я всего лишь указываю на проблемы, с которыми вы можете столкнуться при работе в pdb с кодом, содержащим короткие имена переменных.

NNB: Вот ещё одна полезная команда: h(elp) – при вызове без аргументов, вывести список доступных команд; иначе – вывести справку для команды, переданной в качестве аргумента.

Далее я буду использовать короткие варианты команд, а также объяснять по ходу повествования, как работают другие команды. Но начнём с первой по списку.

1. l(ist) или "мне слишком лень открывать исходник"

l(ist) [first [,last]]
    List source code for the current file. Without arguments, list 11 lines around the current line
    or continue the previous listing. With one argument, list 11 lines starting at that line.
    With two arguments, list the given range; if the second argument is less than the first, it is a count.

NB: Данное описание генерируется командой help с аргументом list. Чтобы воспроизвести это у себя, наберите help l в pdb REPL.

Команда list позволяет просмотреть исходный код текущего модуля, а, используя аргументы команды, можно задать диапазон строк для отображения. Например, это может оказаться полезным, если вы работаете с какой-то неизвестной сторонней библиотекой и пытаетесь разобраться, почему они никак не могут исправить проблему с кодировкой строк. Реальный случай!

NB: Начиная с версии Python 3.2, стала доступна ещё одна команда – ll (long list), которая показывает исходный код исполняемой функции или стекового фрейма. Лично я предпочитаю использовать именно эту команду вместо l, так как она позволяет получить более полное представление о функции, в которой вы находитесь, чем простой вывод 11 строчек кода.

Перейдём к практике. Введите l в командной строке pdb и взгляните на вывод:

(Pdb) l
  4     def main():
  5         print("Add the values of the dice")
  6         print("It's really that easy")
  7         print("What are you doing with your life.")
  8         import pdb; pdb.set_trace()
  9  ->     GameRunner.run()
 10
 11
 12     if __name__ == "__main__":
 13         main()
[EOF]

Если вы хотите увидеть весь файл целиком, можно вызвать команду list, указав интервал строк с 1 по 13:

(Pdb) l 1, 13
  1     from dicegame.runner import GameRunner
  2
  3
  4     def main():
  5         print("Add the values of the dice")
  6         print("It's really that easy")
  7         print("What are you doing with your life.")
  8         import pdb; pdb.set_trace()
  9  ->     GameRunner.run()
 10
 11
 12     if __name__ == "__main__":
 13         main()

К сожалению, один этот файл не даёт нам много информации, однако, мы видим, что здесь вызывается метод run() класса GameRunner. Сейчас вы, наверное, думаете: "Отлично! Я просто, как раньше, добавлю строку инициализации pdb где-то внутри метода run в файле dicegame/runner.py." Да, это сработает, но есть и более простой способ. А именно – использовать команду step, о которой мы поговорим далее.

2. s(tep) или "посмотрим, что делает этот метод..."

s(tep)
    Execute the current line, stop at the first possible occasion
    (either in a function that is called or in the current
    function).

В данный момент мы всё ещё находимся на строке 9, на это указывает символ -> в выводе команды list. Давайте выполним команду step и посмотрим, что произойдёт.

(Pdb) s
--Call--
> /Users/Development/pdb-tutorial/dicegame/runner.py(21)run()
-> @classmethod

Превосходно! Мы переместились в файл runner.py на строчку 21, о чём сообщается в строке вывода > /Users/Development/pdb-tutorial/dicegame/runner.py(21)run(). Проблема в том, что пока у нас нет никакого контекста, поэтому далее используем команду list, чтобы посмотреть код метода.

(Pdb) l
 16             total = 0
 17             for die in self.dice:
 18                 total += 1
 19             return total
 20
 21  ->     @classmethod
 22         def run(cls):
 23             # Probably counts wins or something.
 24             # Great variable name, 10/10.
 25             c = 0
 26             while True:

Супер! Теперь мы знаем немного больше о методе run(), но мы всё ещё на строке 21. Давайте снова выполним step, чтобы "зайти в метод", а затем вызовем list, чтобы узнать нашу текущую позицию.

(Pdb) s
> /Users/Development/pdb-tutorial/dicegame/runner.py(25)run()
-> c = 0
(Pdb) l
 20
 21         @classmethod
 22         def run(cls):
 23             # Probably counts wins or something.
 24             # Great variable name, 10/10.
 25  ->         c = 0
 26             while True:
 27                 runner = cls()
 28
 29                 print("Round {}\n".format(runner.round))
 30

Как видите, мы остановились на объявлении переменной c, вызов которой, в силу не совсем удачного именования, может создать нам серьёзную проблему (вспомните комментарий выше о команде c(ontinue)). Сейчас мы находимся прямо перед циклом while. Давайте зайдём в цикл и посмотрим, что ещё мы сможем выяснить.

3. n(ext) или "надеюсь, эта строчка не выбросит исключение"

n(ext)
    Continue execution until the next line in the current function
    is reached or it returns.

Введите по очереди команды n(ext) и l(ist) (запомните эту комбинацию) и давайте понаблюдаем, что произойдёт.

NB: В отличие от команды s(tep), которая заходит внутрь вызываемой функции и останавливается на её первой строке, команда n(ext) позволяет "пропустить" вызов функции и перейти на следующую строчку, оставаясь в контексте текущей (вызывающей) функции.

(Pdb) n
> /Users/Development/pdb-tutorial/dicegame/runner.py(27)run()
-> while True:
(Pdb) l
 21         @classmethod
 22         def run(cls):
 23             # Probably counts wins or something.
 24             # Great variable name, 10/10.
 25             c = 0
 26  ->         while True:
 27                 runner = cls()
 28
 29                 print("Round {}\n".format(runner.round))
 30
 31                 for die in runner.dice:

Метка текущей строки переместилась на оператор while True! Мы можем вызывать команду next неограниченное число раз до тех пор, пока программа не завершится или не сгенерирует исключение. Повторим next ещё 3 раза, чтобы попасть на цикл for, а затем вызовем команду list.

(Pdb) n
> /Users/Development/pdb-tutorial/dicegame/runner.py(27)run()
-> runner = cls()
(Pdb) n
> /Users/Development/pdb-tutorial/dicegame/runner.py(29)run()
-> print("Round {}\n".format(runner.round))
(Pdb) n
Round 1

> /Users/Development/pdb-tutorial/dicegame/runner.py(31)run()
-> for die in runner.dice:
(Pdb) l
 26             while True:
 27                 runner = cls()
 28
 29                 print("Round {}\n".format(runner.round))
 30
 31  ->             for die in runner.dice:
 32                     print(die.show())
 33
 34                 guess = input("Sigh. What is your guess?: ")
 35                 guess = int(guess)

Последующие вызовы команды next повлекут обход цикла for, причём количество итераций будет равно длине атрибута runner.dice. Мы можем узнать длину runner.dice, используя функцию len() прямо из pdb REPL. В нашем случае функция должна вернуть число 5.

(Pdb) len(runner.dice)
5

Так как длина составляет всего пять элементов, мы могли бы обойти цикл вызвав next 5 раз, но что если бы у нас было 50 элементов для итерации или даже 10,000! Лучшим вариантом будет использование точки останова и команды continue.

4. b(reak) или "я устал каждый раз набирать n"

b(reak) [ ([filename:]lineno | function) [, condition] ]
    Without argument, list all breaks.

    With a line number argument, set a break at this line in the
    current file.  With a function name, set a break at the first
    executable line of that function.  If a second argument is
    present, it is a string specifying an expression which must
    evaluate to true before the breakpoint is honored.

    The line number may be prefixed with a filename and a colon,
    to specify a breakpoint in another file (probably one that
    hasn't been loaded yet).  The file is searched for on
    sys.path; the .py suffix may be omitted.

В данном руководстве мы рассмотрим только первые два абзаца описания команды break.

В конце предыдущего раздела мы говорили о том, чтобы установить точку останова после цикла for для быстрого продвижения по методу run(). Давайте остановимся на строке 34, так как она содержит функцию ввода, которая в любом случае заблокирует выполнение программы в ожидании ввода пользователя. Для этого мы можем ввести команду b 34, а затем использовать continue, чтобы продолжить выполнение до точки останова.

(Pdb) b 34
Breakpoint 1 at /Users/Development/pdb-tutorial/dicegame/runner.py(34)run()
(Pdb) c

[...] # prints some dice

> /Users/Development/pdb-tutorial/dicegame/runner.py(34)run()
-> guess = input("Sigh. What is your guess?: ")

Вызов команды break без аргументов позволяет вывести список всех имеющихся точек останова.

(Pdb) b
Num Type         Disp Enb   Where
1   breakpoint   keep yes   at /Users/Development/pdb-tutorial/dicegame/runner.py:34
    breakpoint already hit 1 time

Чтобы удалить точку останова, можно использовать команду cl(ear), указав в качестве аргумента номер точки останова, узнать который можно из крайнего левого столбца вывода команды break. Давайте удалим точку останова с номером 1.

NB: Чтобы удалить сразу все точки останова, достаточно вызвать команду clear без аргументов.

(Pdb) cl 1
Deleted breakpoint 1 at /Users/Development/pdb-tutorial/dicegame/runner.py:34

Далее выполним команду next, что повлечёт вызов функции input(). Отвечая на вопрос игры, введём число 10 и, после возврата в pdb REPL, выполним list, чтобы увидеть следующие несколько строчек кода.

(Pdb) n
Sigh. What is your guess?: 10
> /Users/Development/pdb-tutorial/dicegame/runner.py(35)run()
-> guess = int(guess)
(Pdb) l
 30
 31                 for die in runner.dice:
 32                     print(die.show())
 33
 34                 guess = input("Sigh. What is your guess?: ")
 35  ->             guess = int(guess)
 36
 37                 if guess == runner.answer():
 38                     print("Congrats, you can add like a 5 year old...")
 39                     runner.wins += 1
 40                     c += 1

На всякий случай напомню, что сейчас мы пытаемся понять, почему при первом прохождении игры наш ответ оказался неверным. Похоже, проблема заключается в том, что условие guess == runner.answer может выполняться некорректно. Нам стоит проверить, что делает метод runner.answer(), на случай, если ошибка содержится именно в нём. Выполним команду next, а затем step, чтобы зайти в метод.

(Pdb) s
--Call--
> /Users/spiro/Development/mobify/engineering-meeting/pdb-tutorial/dicegame/runner.py(15)answer()
-> def answer(self):
(Pdb) l
 10         def reset(self):
 11             self.round = 1
 12             self.wins = 0
 13             self.loses = 0
 14
 15  ->     def answer(self):
 16             total = 0
 17             for die in self.dice:
 18                 total += 1
 19             return total
 20

Думаю, я понял в чём дело! В строке 18 значение переменной total увеличивается на единицу, хотя, по идее, оно должно складываться из количества очков, выпавших на костях. Посмотрим, можно ли это исправить. Для этого мы проверим, имеет ли объект die атрибут, хранящий число очков. Чтобы перейти на строку 18, вы можете установить точку останова или просто вызывать next до первой итерации цикла. После этого воспользуемся функцией dir(), чтобы узнать, какие свойства и методы имеет объект die.

-> total += 1
(Pdb) dir(die)
['__class__', '__delattr__', [...], 'create_dice', 'roll', 'show', 'value']

Кажется, атрибут value – это как раз то, что мы ищем! Давайте посмотрим, что конкретно он возвращает (учтите, что ваше значение скорее всего будет отличаться от того, что получил я), и, для пущей уверенности, убедимся, что это значение совпадает с количеством "точек", отображаемых при вызове метода show().

(Pdb) die.value
2
(Pdb) die.show()
'---------\n|*      |\n|       |\n|      *|\n---------'

NB: Если вы хотите, чтобы символ \n при выводе на экран генерировал новую строку, используйте функцию print(), передавая die.show() как аргумент.

Похоже, всё работает именно так, как ожидалось, и теперь мы можем исправить ошибку в методе answer. Кто-то из вас, возможно, захочет продолжить процесс отладки и попробовать отыскать все недочёты в коде программы за один проход. К сожалению, пока мы в очередной раз застряли в цикле for и, если ваша первая мысль – добавить точку останова на строку 19 и затем вызвать continue, не торопитесь, – как мы увидим далее, в этой ситуации есть способ получше.

5. r(eturn) или "я хочу выбраться из этой функции"

r(eturn)
    Continue execution until the current function returns.

return – крайне полезная команда для продвинутых пользователей, которая позволяет исследовать конечный результат выполнения функции. Хотя зачастую для этого достаточно установить точку останова на строке, в которой функция делает возврат, команда return окажется незаменима в тех случаях, когда функция содержит несколько операторов возврата, поскольку она, следуя пути выполнения, сработает автоматически для любого из них. Давайте выполним return и переместимся в конец текущей функции.

(Pdb) r
--Return--
> /Users/Development/pdb-tutorial/dicegame/runner.py(19)answer()->5
-> return total
(Pdb) l
 14
 15         def answer(self):
 16             total = 0
 17             for die in self.dice:
 18                 total += 1
 19  ->         return total
 20
 21         @classmethod
 22         def run(cls):
 23             # Probably counts wins or something.
 24             # Great variable name, 10/10.
(Pdb)

Для того чтобы узнать значение возвращаемой переменной total, можно ввести total в командной строке отладчика или просто посмотреть последнее значение в строке вывода, следующей за --Return--. Теперь, чтобы вернуться обратно в метод run(), выполните команду next и вот вы снова в знакомом месте!

Прямо сейчас вы можете выйти из pdb с помощью функции exit() или нажав сочетание клавиш CTRL-D (так же как в Python REPL). Используя пять базовых команд, которые мы рассмотрели, вы запросто сможете отыскать ещё несколько ошибок в коде программы, после чего мы перейдём к более сложным примерам использования отладчика.

Команды продвинутого уровня

Рассмотрим ещё несколько команд pdb, которые также могут оказаться полезными для вас в работе.

Команда !

!
  Execute the (one-line) statement in the context of the current stack frame.

Восклицательный знак ! сигнализирует отладчику, что последующая команда должна быть интерпретирована как команда Python, а не команда pdb. Вспомните переменную c в методе run(). Как я отмечал выше, вызов c повлечёт выполнение continue, так как команды pdb имеют приоритет. В этой ситуации, чтобы вывести значение переменной c, следует использовать команду !. Перемещаясь по коду в pdb REPL, остановитесь на строке 26 файла runner.py и введите c с префиксом !.

(Pdb) !c
0

Так как на строке 25 происходит присваивание c = 0, мы получили ожидаемый результат.

Команда commands

commands [bpnumber]
        (com) ...
        (com) end
        (Pdb)

        Specify a list of commands for breakpoint number bpnumber.

Команда commands позволяет выполнять другие команды pdb или код на Python автоматически, каждый раз при достижении указанной точки останова. Команды (или код) выполняются в заданном порядке, так, как если бы вы вводили их вручную в командной строке отладчика. При наборе команд в блоке commands приглашение командной строки меняется на (com). Для завершения блока нужно ввести слово end, при этом приглашение командной строки изменится обратно с (com) на (Pdb).

Эта команда оказывается очень полезной, например, в тех случаях, когда нужно проверять значения переменных внутри цикла, поскольку она избавляет от необходимости выводить эти значения напрямую на каждой итерации. Давайте рассмотрим пример. Убедитесь, что вы находитесь в корневом директории проекта, и введите в терминале

python -m pdb main.py

Перейдите на строку 8 и выполните s(tep), чтобы попасть в метод run() класса GameRunner. Затем добавьте точку останова на строку 17.

> /Users/Development/pdb-tutorial/main.py(8)main()
-> GameRunner.run()  #This is line 8 in main.py
(Pdb) s
--Call--
> /Users/Development/pdb-tutorial/dicegame/runner.py(21)run()
-> @classmethod
(Pdb) b 17
Breakpoint 4 at /Users/Development/pdb-tutorial/dicegame/runner.py:17

Точка останова, которой был присвоен номер 4, была установлена в начало цикла в методе answer(), который, как мы помним, вычисляет суммарное число очков, выпавших на костях. Используем команду commands, чтобы выводить значение переменной total всякий раз при срабатывании этой точки останова.

(Pdb) commands 4
(com) print(f"The total value as of now is {total}")
(com) end

С помощью commands мы задали команду (в данном случае – это вызов функции print), которая будет выполняться при достижении точки останова 4. Чтобы попасть на эту точку останова, используем команду c(ontinue)

(Pdb) c
[...] # You will have to guess a number
The total value as of now is 0
> /Users/Development/pdb-tutorial/dicegame/runner.py(17)answer()
-> for die in self.dice:
(Pdb)

Мы видим, что при срабатывании точки останова происходит вывод на экран текущего значения переменной total. Выполним c(ontinue) ещё раз и посмотрим, что произойдёт

(Pdb) c
[...]
The total value as of now is 1
> /Users/Development/pdb-tutorial/dicegame/runner.py(17)answer()
-> for die in self.dice:
(Pdb)

И вновь срабатывание точки останова повлекло выполнение команды в блоке commands (а именно функции print). Думаю, вы уже поняли, насколько это может быть полезно, особенно при работе с циклами.

Пост Мортем

pdb.post_mortem(traceback=None)
    Enter post-mortem debugging of the given traceback object. If no traceback is given, it uses the one of the exception that is currently being handled
    (an exception must be being handled if the default is to be used).

pdb.pm()
    Enter post-mortem debugging of the traceback found in sys.last_traceback. 

Методы post_mortem() и pm() очень похожи. Отличие между ними состоит в том, что они обрабатывают разные объекты, содержащие трассировку стека вызовов. Лично я обычно использую post_mortem() в блоке except, однако здесь мы рассмотрим метод pm(), так как, по моему мнению, он обладает более широкими возможностями. Посмотрим, как это работает на практике.

Перейдите в корень проекта и введите python в командной строке, чтобы запустить Python REPL. Далее мы импортируем метод main из модуля main, а также модуль pdb. После вызова main() будем играть до тех пор, пока программа не выбросит исключение при попытке ввести Y для продолжения игры.

>>> import pdb
>>> from main import main
>>> main()
[...]
Would you like to play again?[Y/n]: Y
Traceback (most recent call last):
  File "main.py", line 12, in <module>
    main()
  File "main.py", line 8, in main
    GameRunner.run()
  File "/Users/Development/pdb-tutorial/dicegame/runner.py", line 62, in run
    i_just_throw_an_exception()
  File "/Users/Development/pdb-tutorial/dicegame/utils.py", line 13, in i_just_throw_an_exception
    raise UnnecessaryError("You actually called this function...")
dicegame.utils.UnnecessaryError: You actually called this function...

Теперь давайте вызовем метод pm() из модуля pdb и посмотрим, что произойдёт.

>>> pdb.pm()
> /Users/Development/pdb-tutorial/dicegame/utils.py(13)i_just_throw_an_exception()
-> raise UnnecessaryError("You actually called this function...")
(Pdb)

Только взгляните на это! Отладчик запустился автоматически и текущая строка указывает на то место в коде, откуда было сгенерировано исключение. Это позволит нам исследовать состояние программы в момент, когда произошла аварийная остановка, и, в конечном итоге, поможет найти причину ошибки.

NB: Также вы можете запустить скрипт main.py с помощью команды python -m pdb main.py, а затем выполнять continue вплоть до остановки программы. Python автоматически перейдёт в режим пост мортем, когда программа выбросит неперехваченное исключение.

Заключение

Поздравляю с завершением и спасибо, что дочитали эту инструкцию до конца! Если у вас есть какие-либо комментарии, замечания или вы хотели бы предложить дополнительные примеры, оставляйте ваши PR в репозитории проекта - я всегда открыт для обратной связи.

About

Краткая инструкция по эффективному использованию pdb

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • Python 100.0%