pdb – Интерактивный отладчик

http://www.doughellmann.com/PyMOTW/pdb/
  • Перевод

pdb – Интерактивный отладчик


Версии Python: 1.4 и выше

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

Запуск отладчика

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

Из командной строки

Самый очевидный метод использования отладчика — запускать его из командной строки, передавая вашу программу параметром так, чтобы он знал, что запускать.
  1. # encoding: utf-8
  2. #
  3. # Copyright (c) 2010 Doug Hellmann. All rights reserved.
  4. #
  5.  
  6. class MyObj(object):
  7.  
  8.   def __init__(self, num_loops):
  9.     self.count = num_loops
  10.  
  11.   def go(self):
  12.     for i in range(self.count):
  13.       print i
  14.     return
  15.  
  16. if __name__ == '__main__':
  17.   MyObj(5).go()
* This source code was highlighted with Source Code Highlighter.

При запуске отладчика из командной строки он загружает ваш исходник и останавливает выполнение на первом найденном выражении. В нашем случае он остановится перед выполнением определения класса MyObj в седьмой строке.

$ python -m pdb pdb_script.py
> .../pdb_script.py(7)<module>()
-> class MyObj(object):
(Pdb)

Note:
Обычно pdb включает полный путь к каждому модулю при выводе пути файла. Для соблюдения лаконичности примеров в выводе отладчика эти пути заменены на …

Из интерпретатора

Многие разработчики работают с интерактивным интерпретатором на ранних стадиях разработки модулей, так как это позволяет им более итеративно экспериментировать без необходимости повторения цикла save/run/repeat, необходимого при создании самостоятельных скриптов. Для запуска отладчика из интерактивной сессии используйте run() или runeval().

$ python
Python 2.7 (r27:82508, Jul 3 2010, 21:12:11)
[GCC 4.0.1 (Apple Inc. build 5493)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import pdb_script
>>> import pdb
>>> pdb.run('pdb_script.MyObj(5).go()')
> <string>(1)<module>()
(Pdb)


Аргумент у run() — это строковое выражение, которое может быть выполнено интерпретатором Python. Отладчик распарсит его и остановит выполнение как раз перед запуском первого выражения. Для навигации и управления выполнением вы можете использовать команды, описанные ниже.

Из вашей программы

Оба предыдущих примера основывались на том, что вы хотите запустить отладчик в самом начале вашей программы. Для более сложных процессов, в которых проблема возникает намного позже, во время выполнения, удобнее запускать отладчик изнутри программы используя set_trace().
  1. #!/usr/bin/env python
  2. # encoding: utf-8
  3. #
  4. # Copyright (c) 2010 Doug Hellmann. All rights reserved.
  5. #
  6.  
  7. import pdb
  8.  
  9. class MyObj(object):
  10.  
  11.   def __init__(self, num_loops):
  12.     self.count = num_loops
  13.  
  14.   def go(self):
  15.     for i in range(self.count):
  16.       pdb.set_trace()
  17.       print i
  18.     return
  19.  
  20. if __name__ == '__main__':
  21.   MyObj(5).go()

В 16-й строке тестовый скрипт вызывает отладчик.

$ python ./pdb_set_trace.py
> .../pdb_set_trace.py(17)go()
-> print i
(Pdb)


set_trace() – это просто функция в Python, так что её можно вызывать в любой момент исполнения программы. Это позволяет попадать в режим отладки, в зависимости от условий, сложившихся внутри программы, например при обработке исключений, или в особой ветке какого-либо управляющего выражения

После сбоя

Отладка программы после её завершения называется отладкой post-mortem. pdb поддерживает такую отладку с помощью функций pm() и post_mortem().
  1. #!/usr/bin/env python
  2. # encoding: utf-8
  3. #
  4. # Copyright (c) 2010 Doug Hellmann. All rights reserved.
  5. #
  6.  
  7. class MyObj(object):
  8.  
  9.   def __init__(self, num_loops):
  10.     self.count = num_loops
  11.  
  12.   def go(self):
  13.     for i in range(self.num_loops):
  14.       print i
  15.     return

Тут, на 13-й строке неправильное имя поля вызовет исключение AttributeError, это прекратит выполнение. pm() смотрит на интерактивный трейсбек и запускает отладчик в том фрейме стека, в котором произошло исключение.

$ python
Python 2.7 (r27:82508, Jul 3 2010, 21:12:11)
[GCC 4.0.1 (Apple Inc. build 5493)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> from pdb_post_mortem import MyObj
>>> MyObj(5).go()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "pdb_post_mortem.py", line 13, in go
for i in range(self.num_loops):
AttributeError: 'MyObj' object has no attribute 'num_loops'
>>> import pdb
>>> pdb.pm()
> .../pdb_post_mortem.py(13)go()
-> for i in range(self.num_loops):
(Pdb)


Управление отладчиком

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

Навигация по стеку вызовов

В любой момент работы отладчика вы можете использовать where (или просто w) для уточнения, какая строка исполняется в данный момент, и где вы находитесь в стеке вызовов. В этом случае — модуль pdb_set_trace.py строка 17 в методе go().

$ python pdb_set_trace.py
> .../pdb_set_trace.py(17)go()
-> print i
(Pdb) where
.../pdb_set_trace.py(21)<module>()
-> MyObj(5).go()
> .../pdb_set_trace.py(17)go()
-> print i


Чтобы ознакомиться с контекстом текущего места, используйте list (l).

(Pdb) list
12 self.count = num_loops
13
14 def go(self):
15 for i in range(self.count):
16 pdb.set_trace()
17 -> print i
18 return
19
20 if __name__ == '__main__':
21 MyObj(5).go()
[EOF]
(Pdb)


По умолчанию, выводится 11 строк вокруг текущей (5 выше и 5 ниже). При вызове list с единым аргументом, она выведет 11 строк, соседних с заданной, вместо текущей.

(Pdb) list 14
9 class MyObj(object):
10
11 def __init__(self, num_loops):
12 self.count = num_loops
13
14 def go(self):
15 for i in range(self.count):
16 pdb.set_trace()
17 -> print i
18 return
19


Если передать list два аргумента, то они будут обработаны, как первая и последняя строки, которые будут выведены на экран.

(Pdb) list 5, 19
5 #
6
7 import pdb
8
9 class MyObj(object):
10
11 def __init__(self, num_loops):
12 self.count = num_loops
13
14 def go(self):
15 for i in range(self.count):
16 pdb.set_trace()
17 -> print i
18 return
19


Перемещайтесь по стеку вызовов, используя up и down. up (коротко u) перемещает к более ранним вызовам в стеке. down (или d) переносит к более глубоким вызовам.

(Pdb) up
> .../pdb_set_trace.py(21)<module>()
-> MyObj(5).go()

(Pdb) down
> .../pdb_set_trace.py(17)go()
-> print i


При каждом перемещении по стеку, отладчик выводит текущее местонахождение в том же формате, что и where.

Просмотр переменных в стеке

С каджым фреймом стека связан набор переменных, включая значения, локальные для исполняемой функции и информацию о глобальном состоянии. pdb позволяет несколькимя способами просмотреть содержимое этих переменных.
  1. #!/usr/bin/env python
  2. # encoding: utf-8
  3. #
  4. # Copyright (c) 2010 Doug Hellmann. All rights reserved.
  5. #
  6.  
  7. import pdb
  8.  
  9. def recursive_function(n=5, output='to be printed'):
  10.   if n > 0:
  11.     recursive_function(n-1)
  12.   else:
  13.     pdb.set_trace()
  14.     print output
  15.   return
  16.  
  17. if __name__ == '__main__':
  18.   recursive_function()


Команда args (она же a) выводит все аргументы функции, активной в текущем фрейме. В этом примере также используется рекурсивная функция для демонстрации того, как выглядит глубокий стек в выводе where.

$ python pdb_function_arguments.py
> .../pdb_function_arguments.py(14)recursive_function()
-> return
(Pdb) where
.../pdb_function_arguments.py(17)<module>()
-> recursive_function()
.../pdb_function_arguments.py(11)recursive_function()
-> recursive_function(n-1)
.../pdb_function_arguments.py(11)recursive_function()
-> recursive_function(n-1)
.../pdb_function_arguments.py(11)recursive_function()
-> recursive_function(n-1)
.../pdb_function_arguments.py(11)recursive_function()
-> recursive_function(n-1)
.../pdb_function_arguments.py(11)recursive_function()
-> recursive_function(n-1)
> .../pdb_function_arguments.py(14)recursive_function()
-> return

(Pdb) args
n = 0
output = to be printed

(Pdb) up
> .../pdb_function_arguments.py(11)recursive_function()
-> recursive_function(n-1)

(Pdb) args
n = 1
output = to be printed

(Pdb)


Команда p выполняет выражение, переданное аргументом и выводит результат. Вы также можете использовать выражение print, но оно передаётся интерпретатору Python для выполнения, вместо выполнения, как команда отладчика.

(Pdb) p n
1

(Pdb) print n
1


Аналогично, начиная выражение с ! вы сразу отправляете его в интерпретатор Python к исполнению. Эту возможность можно использовать для выполнения произвольных Python команд, в том числе изменение переменных. В этом примере значение output изменяется прежде, чем позволить отладчику продолжить исполнение программы. Следующий вызов, после set_trace() выводит output, отображая изменённое значение.

$ python pdb_function_arguments.py
> .../pdb_function_arguments.py(14)recursive_function()
-> print output

(Pdb) !output
'to be printed'

(Pdb) !output='changed value'

(Pdb) continue
changed value


Для более сложных значений, таких как вложенные или большие структуры данных, используйте pp (pretty print) для «красивого» вывода. Эта программа читает несколько строк текста из файла.
  1. #!/usr/bin/env python
  2. # encoding: utf-8
  3. #
  4. # Copyright (c) 2010 Doug Hellmann. All rights reserved.
  5. #
  6.  
  7. import pdb
  8.  
  9. with open('lorem.txt', 'rt') as f:
  10.   lines = f.readlines()
  11.  
  12. pdb.set_trace()

Вывод строк при помощи p получается сложночитаемым, т.к. он переносится неестественно. pp использует pprint для форматирования значений перед выводом.

$ python pdb_pp.py
--Return--
> .../pdb_pp.py(12)<module>()->None
-> pdb.set_trace()
(Pdb) p lines
['Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Donec\n', 'egestas, enim
et consectetuer ullamcorper, lectus ligula rutrum leo, a\n', 'elementum elit tortor
eu quam.\n']

(Pdb) pp lines
['Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Donec\n',
'egestas, enim et consectetuer ullamcorper, lectus ligula rutrum leo, a\n',
'elementum elit tortor eu quam.\n']

(Pdb)


Шагание по программе

Кроме навигации вверх и вниз по стеку, пока программа приостановлена, вы также можете продолжить выполнение по шагам с места, где вызывается отладчик.
  1. #!/usr/bin/env python
  2. # encoding: utf-8
  3. #
  4. # Copyright (c) 2010 Doug Hellmann. All rights reserved.
  5. #
  6.  
  7. import pdb
  8.  
  9. def f(n):
  10.   for i in range(n):
  11.     j = i * n
  12.     print i, j
  13.   return
  14.  
  15. if __name__ == '__main__':
  16.   pdb.set_trace()
  17.   f(5)

Используйте step, чтобы выполнить текущую строку и остановиться перед следующей исполняемой точкой — или первым выражением внутри вызываемой функции, или следующей строкой в текущей функции.

$ python pdb_step.py
> /Users/dhellmann/Documents/PyMOTW/src.pdb/PyMOTW/pdb/pdb_step.py(17)<module>()
-> f(5)


Интерпретатор останавливается на вызове set_trace() и передаёт управление отладчику. В результате первого вызова step выполняется вызов f().

(Pdb) step
--Call--
> .../pdb_step.py(9)f()
-> def f(n):


Ещё раз step, и текущей строкой становится первая в функции f() и начинает выполняться цикл.

(Pdb) step
> .../pdb_step.py(10)f()
-> for i in range(n):


Перешагнув ещё раз, попадаем на первую строку внутри цикла, где определяется j.

(Pdb) step
> /Users/dhellmann/Documents/PyMOTW/src.pdb/PyMOTW/pdb/pdb_step.py(11)f()
-> j = i * n
(Pdb) p i
0


Значение i — 0, поэтому после этого шага значение j тоже должно быть 0.

(Pdb) step
> /Users/dhellmann/Documents/PyMOTW/src.pdb/PyMOTW/pdb/pdb_step.py(12)f()
-> print i, j

(Pdb) p j
0

(Pdb)


Перешагивать снова и снова может быть утомительно, если предстоит преодолеть много кода до места ошибки, или если функция повторно вызывается.
  1. #!/usr/bin/env python
  2. # encoding: utf-8
  3. #
  4. # Copyright (c) 2010 Doug Hellmann. All rights reserved.
  5. #
  6.  
  7. import pdb
  8.  
  9. def calc(i, n):
  10.   j = i * n
  11.   return j
  12.  
  13. def f(n):
  14.   for i in range(n):
  15.     j = calc(i, n)
  16.     print i, j
  17.   return
  18.  
  19. if __name__ == '__main__':
  20.   pdb.set_trace()
  21.   f(5)

В этом примере нет никаких проблем с calc(), так что проход по всем её строкам, при каждом вызове в цикле в f(), только засоряет полезный вывод, отображая все строки calc() по мере их выполнения.

$ python pdb_next.py
> .../pdb_next.py(21)<module>()
-> f(5)
(Pdb) step
--Call--
> .../pdb_next.py(13)f()
-> def f(n):

(Pdb) step
> .../pdb_next.py(14)f()
-> for i in range(n):

(Pdb) step
> .../pdb_next.py(15)f()
-> j = calc(i, n)

(Pdb) step
--Call--
> .../pdb_next.py(9)calc()
-> def calc(i, n):

(Pdb) step
> .../pdb_next.py(10)calc()
-> j = i * n

(Pdb) step
> .../pdb_next.py(11)calc()
-> return j

(Pdb) step
--Return--
> .../pdb_next.py(11)calc()->0
-> return j

(Pdb) step
> .../pdb_next.py(16)f()
-> print i, j

(Pdb) step
0 0


Команда next похожа на step, но не входит в функции, вызываемые текущим выражением. В результате, после выполнения этой команды, отладчик переходит к следующему выражению текущей функции.

> .../pdb_next.py(14)f()
-> for i in range(n):
(Pdb) step
> .../pdb_next.py(15)f()
-> j = calc(i, n)

(Pdb) next
> .../pdb_next.py(16)f()
-> print i, j

(Pdb)


Команда until — как next, только она продолжает выполнение, пока оно не достигнет строки текущей функции, расположенной ниже (с номером выше, чем у текущей). Это значит, что, например, until может использоваться для выхода из циклов.

$ python pdb_next.py
> .../pdb_next.py(21)<module>()
-> f(5)
(Pdb) step
--Call--
> .../pdb_next.py(13)f()
-> def f(n):

(Pdb) step
> .../pdb_next.py(14)f()
-> for i in range(n):

(Pdb) step
> .../pdb_next.py(15)f()
-> j = calc(i, n)

(Pdb) next
> .../pdb_next.py(16)f()
-> print i, j

(Pdb) until
0 0
1 5
2 10
3 15
4 20
> .../pdb_next.py(17)f()
-> return

(Pdb)


До выполнения until, текущая строка была 16 — последняя строка цикла. После запуска until, исполнение дошло до 17-й строки и цикл исчерпался.

return — другой способ пропустить части функции. Он продолжает исполнение до момента, когда должен выполниться возврат из текщуей функции, и приостанавливает программу. Это даёт время посмотреть на возвращаемое значение перед возвратом из функции.

$ python pdb_next.py
> .../pdb_next.py(21)<module>()
-> f(5)
(Pdb) step
--Call--
> .../pdb_next.py(13)f()
-> def f(n):

(Pdb) step
> .../pdb_next.py(14)f()
-> for i in range(n):

(Pdb) return
0 0
1 5
2 10
3 15
4 20
--Return--
> .../pdb_next.py(17)f()->None
-> return

(Pdb)


Точки остановки

Когда программы становятся ещё больше, даже использование next и until станет медленным и скучным. Вместо построчного перешагивания по программе, есть решение получше — позволить ей выполняться нормально до момента, когда она достигнет точки, в который вам необходимо прервать её. Вы можете использовать set_trace() для запуска отладчика, но это работает, только если вы хотите остановить программу в одном месте. Более удобное решение — это запускать всю программу в отладчике, но заранее указать ему, где останавливаться, используя брейкпоинты. Отладчик следит за выполнением программы, и, когда она достигает описанного брейкпоинтом места, останавливает выполнение перед обозначенной строкой.
  1. #!/usr/bin/env python
  2. # encoding: utf-8
  3. #
  4. # Copyright (c) 2010 Doug Hellmann. All rights reserved.
  5. #
  6.  
  7. def calc(i, n):
  8.   j = i * n
  9.   print 'j =', j
  10.   if j > 0:
  11.     print 'Positive!'
  12.   return j
  13.  
  14. def f(n):
  15.   for i in range(n):
  16.     print 'i =', i
  17.     j = calc(i, n)
  18.   return
  19.  
  20. if __name__ == '__main__':
  21.   f(5)

Команда break принимает несколько опций для установки брейкпоинтов. Вы можете указать номер строки, файл или функцию, где исполнение должно остановиться. Для установки брейкпоинта на конкретной строке текущего файла, используйте break lineno:

$ python -m pdb pdb_break.py
> .../pdb_break.py(7)<module>()
-> def calc(i, n):
(Pdb) break 11
Breakpoint 1 at .../pdb_break.py:11

(Pdb) continue
i = 0
j = 0
i = 1
j = 5
> .../pdb_break.py(11)calc()
-> print 'Positive!'

(Pdb)

Команда continue сообщает отладчику о продолжении выполнения программы до следующего брейкпоинта. В этом случае, первая итерация цикла в f() пройдёт, и остановится в calc() на второй итерации.

Брейкпоинты также могут быть установлены на первую строку функции, если указать её имя вместо номера строки. Следующий пример покажет, что произойдёт, если добавить брейкпоинт в функцию calc().

$ python -m pdb pdb_break.py
> .../pdb_break.py(7)<module>()
-> def calc(i, n):
(Pdb) break calc
Breakpoint 1 at .../pdb_break.py:7

(Pdb) continue
i = 0
> .../pdb_break.py(8)calc()
-> j = i * n

(Pdb) where
.../pdb_break.py(21)<module>()
-> f(5)
.../pdb_break.py(17)f()
-> j = calc(i, n)
> .../pdb_break.py(8)calc()
-> j = i * n

(Pdb)


Для указания брейкпоинта в другом файле, предварите номер строки, или название функции именем файла.
  1. #!/usr/bin/env python
  2. # encoding: utf-8
  3. #
  4. # Copyright (c) 2010 Doug Hellmann. All rights reserved.
  5. #
  6.  
  7. from pdb_break import f
  8.  
  9. f(5)

Тут устанавливаем брейпоинт на 11-ю строку pdb_break.py после запуска основной программы pdb_break_remote.py.

$ python -m pdb pdb_break_remote.py
> .../pdb_break_remote.py(7)<module>()
-> from pdb_break import f
(Pdb) break pdb_break.py:11
Breakpoint 1 at .../pdb_break.py:11

(Pdb) continue
i = 0
j = 0
i = 1
j = 5
> .../pdb_break.py(11)calc()
-> print 'Positive!'

(Pdb)


Имя файла может быть полным путём к исходнику, или относительным путём к файлу, доступному в sys.path.

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

$ python -m pdb pdb_break.py
> .../pdb_break.py(7)<module>()
-> def calc(i, n):
(Pdb) break 11
Breakpoint 1 at .../pdb_break.py:11

(Pdb) break
Num Type Disp Enb Where
1 breakpoint keep yes at .../pdb_break.py:11

(Pdb) continue
i = 0
j = 0
i = 1
j = 5
> .../pdb/pdb_break.py(11)calc()
-> print 'Positive!'

(Pdb) continue
Positive!
i = 2
j = 10
> .../pdb_break.py(11)calc()
-> print 'Positive!'

(Pdb) break
Num Type Disp Enb Where
1 breakpoint keep yes at .../pdb_break.py:11
breakpoint already hit 2 times

(Pdb)


Управление брейкпоинтами

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

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

$ python -m pdb pdb_break.py
> .../pdb_break.py(7)<module>()
-> def calc(i, n):
(Pdb) break calc
Breakpoint 1 at .../pdb_break.py:7

(Pdb) break 11
Breakpoint 2 at .../pdb_break.py:11

(Pdb) break
Num Type Disp Enb Where
1 breakpoint keep yes at .../pdb_break.py:7
2 breakpoint keep yes at .../pdb_break.py:11

(Pdb) disable 1

(Pdb) break
Num Type Disp Enb Where
1 breakpoint keep no at .../pdb_break.py:7
2 breakpoint keep yes at .../pdb_break.py:11

(Pdb) continue
i = 0
j = 0
i = 1
j = 5
> .../pdb_break.py(11)calc()
-> print 'Positive!'

(Pdb)


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

$ python -m pdb pdb_break.py
> .../pdb_break.py(7)<module>()
-> def calc(i, n):
(Pdb) break calc
Breakpoint 1 at .../pdb_break.py:7

(Pdb) break 16
Breakpoint 2 at .../pdb_break.py:16

(Pdb) disable 1

(Pdb) continue
> .../pdb_break.py(16)f()
-> print 'i =', i

(Pdb) list
11 print 'Positive!'
12 return j
13
14 def f(n):
15 for i in range(n):
16 B-> print 'i =', i
17 j = calc(i, n)
18 return
19
20 if __name__ == '__main__':
21 f(5)

(Pdb) continue
i = 0
j = 0
> .../pdb_break.py(16)f()
-> print 'i =', i

(Pdb) list
11 print 'Positive!'
12 return j
13
14 def f(n):
15 for i in range(n):
16 B-> print 'i =', i
17 j = calc(i, n)
18 return
19
20 if __name__ == '__main__':
21 f(5)

(Pdb) p i
1

(Pdb) enable 1

(Pdb) continue
i = 1
> .../pdb_break.py(8)calc()
-> j = i * n

(Pdb) list
3 #
4 # Copyright (c) 2010 Doug Hellmann. All rights reserved.
5 #
6
7 B def calc(i, n):
8 -> j = i * n
9 print 'j =', j
10 if j > 0:
11 print 'Positive!'
12 return j
13

(Pdb)


Строки, перед которыми указано B в выводе list показывают места программы, где установлены брейкпоинты (строки 9 и 18).

Используйте clear для полного удаления брейкпоинта.

$ python -m pdb pdb_break.py
> .../pdb_break.py(7)<module>()
-> def calc(i, n):
(Pdb) break calc
Breakpoint 1 at .../pdb_break.py:7

(Pdb) break 11
Breakpoint 2 at .../pdb_break.py:11

(Pdb) break 16
Breakpoint 3 at .../pdb_break.py:16

(Pdb) break
Num Type Disp Enb Where
1 breakpoint keep yes at .../pdb_break.py:7
2 breakpoint keep yes at .../pdb_break.py:11
3 breakpoint keep yes at .../pdb_break.py:16

(Pdb) clear 2
Deleted breakpoint 2

(Pdb) break
Num Type Disp Enb Where
1 breakpoint keep yes at .../pdb_break.py:7
3 breakpoint keep yes at .../pdb_break.py:16

(Pdb)


Остальные брейпоинты сохранят свои идентификаторы и не перенумеровываются.

Временные брейкпоинты

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

$ python -m pdb pdb_break.py
> .../pdb_break.py(7)<module>()
-> def calc(i, n):
(Pdb) tbreak 11
Breakpoint 1 at .../pdb_break.py:11

(Pdb) continue
i = 0
j = 0
i = 1
j = 5
Deleted breakpoint 1
> .../pdb_break.py(11)calc()
-> print 'Positive!'

(Pdb) break

(Pdb) continue
Positive!
i = 2
j = 10
Positive!
i = 3
j = 15
Positive!
i = 4
j = 20
Positive!
The program finished and will be restarted
> .../pdb_break.py(7)<module>()
-> def calc(i, n):

(Pdb)


После того, как выполнение дошло до 11-й строки в первый раз, брейкпоинт убирается и программа работает без остановок до конца.

Брейкпоинты с условием

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

Условные брейкпоинты могут быть установлены одним из двух способов. Первый — указать условие в момент создания брейкпоинта используя break.

$ python -m pdb pdb_break.py
> .../pdb_break.py(7)<module>()
-> def calc(i, n):
(Pdb) break 9, j>0
Breakpoint 1 at .../pdb_break.py:9

(Pdb) break
Num Type Disp Enb Where
1 breakpoint keep yes at .../pdb_break.py:9
stop only if j>0

(Pdb) continue
i = 0
j = 0
i = 1
> .../pdb_break.py(9)calc()
-> print 'j =', j

(Pdb)


Аргумент условия должен быть выражением, использующим имена переменных, видимые в том фрейме стека, в котором брейкпоинт определён. Если выражение сводится к значению True, то выполнение программы останавливается на брейкпоинте.

Условие может также быть применено к существующему брейкпоинту, используя команду condition. Аргумент — id брейкпоинта и выражение.

$ python -m pdb pdb_break.py
> .../pdb_break.py(7)<module>()
-> def calc(i, n):
(Pdb) break 9
Breakpoint 1 at .../pdb_break.py:9

(Pdb) break
Num Type Disp Enb Where
1 breakpoint keep yes at .../pdb_break.py:9

(Pdb) condition 1 j>0

(Pdb) break
Num Type Disp Enb Where
1 breakpoint keep yes at .../pdb_break.py:9
stop only if j>0

(Pdb)


Игнорирование брейкпоинтов

Программы с большим количеством циклов или рекурсивных вызовов часто проще отлаживать «прокручивая» некоторые этапы выполнения программы, вместо просмотра каждого вызова и брейкпоинта. Команда ignore указывает отладчику пропустить некоторый брейкпоинт. При каждом прохождении через брейкпоинт, он уменьшает счётчик игнорирования. Когда он доходит до 0, брейкпоинт снова активируется.

$ python -m pdb pdb_break.py
> .../pdb_break.py(7)<module>()
-> def calc(i, n):
(Pdb) break 17
Breakpoint 1 at .../pdb_break.py:17

(Pdb) continue
i = 0
> .../pdb_break.py(17)f()
-> j = calc(i, n)

(Pdb) next
j = 0
> .../pdb_break.py(15)f()
-> for i in range(n):

(Pdb) ignore 1 2
Will ignore next 2 crossings of breakpoint 1.

(Pdb) break
Num Type Disp Enb Where
1 breakpoint keep yes at .../pdb_break.py:17
ignore next 2 hits
breakpoint already hit 1 time

(Pdb) continue
i = 1
j = 5
Positive!
i = 2
j = 10
Positive!
i = 3
> .../pdb_break.py(17)f()
-> j = calc(i, n)

(Pdb) break
Num Type Disp Enb Where
1 breakpoint keep yes at .../pdb_break.py:17
breakpoint already hit 4 times


Явная установка счётчика в ноль сразу же активирует брейпоинт.

$ python -m pdb pdb_break.py
> .../pdb_break.py(7)<module>()
-> def calc(i, n):
(Pdb) break 17
Breakpoint 1 at .../pdb_break.py:17

(Pdb) ignore 1 2
Will ignore next 2 crossings of breakpoint 1.

(Pdb) break
Num Type Disp Enb Where
1 breakpoint keep yes at .../pdb_break.py:17
ignore next 2 hits

(Pdb) ignore 1 0
Will stop next time breakpoint 1 is reached.

(Pdb) break
Num Type Disp Enb Where
1 breakpoint keep yes at .../pdb_break.py:17


Запуск действий по брейкпоиинту

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

$ python -m pdb pdb_break.py
> .../pdb_break.py(7)<module>()
-> def calc(i, n):
(Pdb) break 9
Breakpoint 1 at .../pdb_break.py:9

(Pdb) commands 1
(com) print 'debug i =', i
(com) print 'debug j =', j
(com) print 'debug n =', n
(com) end

(Pdb) continue
i = 0
debug i = 0
debug j = 0
debug n = 5
> .../pdb_break.py(9)calc()
-> print 'j =', j

(Pdb) continue
j = 0
i = 1
debug i = 1
debug j = 5
debug n = 5
> .../pdb_break.py(9)calc()
-> print 'j =', j

(Pdb)


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

Управление ходом выполнения

Команда jump позволяет управлять выполнением программы на лету, не меняя исходного кода. Вы можете перескочить часть программы вперёд, избегая выполнения некоторого кода, или назад, для повторного его выполнения. Простая программа для генерации списка чисел.
  1. #!/usr/bin/env python
  2. # encoding: utf-8
  3. #
  4. # Copyright (c) 2010 Doug Hellmann. All rights reserved.
  5. #
  6.  
  7. def f(n):
  8.   result = []
  9.   j = 0
  10.   for i in range(n):
  11.     j = i * n + j
  12.     j += n
  13.     result.append(j)
  14.   return result
  15.  
  16. if __name__ == '__main__':
  17.   print f(5)

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

$ python pdb_jump.py

[5, 15, 30, 50, 75]


Переход вперёд

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

$ python -m pdb pdb_jump.py
> .../pdb_jump.py(7)<module>()
-> def f(n):
(Pdb) break 12
Breakpoint 1 at .../pdb_jump.py:12

(Pdb) continue
> .../pdb_jump.py(12)f()
-> j += n

(Pdb) p j
0

(Pdb) step
> .../pdb_jump.py(13)f()
-> result.append(j)

(Pdb) p j
5

(Pdb) continue
> .../pdb_jump.py(12)f()
-> j += n

(Pdb) jump 13
> .../pdb_jump.py(13)f()
-> result.append(j)

(Pdb) p j
10

(Pdb) disable 1

(Pdb) continue
[5, 10, 25, 45, 70]

The program finished and will be restarted
> .../pdb_jump.py(7)<module>()
-> def f(n):
(Pdb)


Переход назад

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

$ python -m pdb pdb_jump.py
> .../pdb_jump.py(7)<module>()
-> def f(n):
(Pdb) break 13
Breakpoint 1 at .../pdb_jump.py:13

(Pdb) continue
> .../pdb_jump.py(13)f()
-> result.append(j)

(Pdb) p j
5

(Pdb) jump 12
> .../pdb_jump.py(12)f()
-> j += n

(Pdb) continue
> .../pdb_jump.py(13)f()
-> result.append(j)

(Pdb) p j
10

(Pdb) disable 1

(Pdb) continue
[10, 20, 35, 55, 80]

The program finished and will be restarted
> .../pdb_jump.py(7)<module>()
-> def f(n):
(Pdb)

Запрещённые переходы

Переходы «в» и «из» некоторых выражений, управляющих потоком вычислений являются опасными или неоднозначными, и, потому, запрещены отладчиком.
  1. #!/usr/bin/env python
  2. # encoding: utf-8
  3. #
  4. # Copyright (c) 2010 Doug Hellmann. All rights reserved.
  5. #
  6.  
  7. def f(n):
  8.   if n < 0:
  9.     raise ValueError('Invalid n: %s' % n)
  10.   result = []
  11.   j = 0
  12.   for i in range(n):
  13.     j = i * n + j
  14.     j += n
  15.     result.append(j)
  16.   return result
  17.  
  18.  
  19. if __name__ == '__main__':
  20.   try:
  21.     print f(5)
  22.   finally:
  23.     print 'Always printed'
  24.  
  25.   try:
  26.     print f(-5)
  27.   except:
  28.     print 'There was an error'
  29.   else:
  30.     print 'There was no error'
  31.  
  32.   print 'Last statement'

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

$ python -m pdb pdb_no_jump.py
> .../pdb_no_jump.py(7)<module>()
-> def f(n):
(Pdb) break 21
Breakpoint 1 at .../pdb_no_jump.py:21

(Pdb) jump 8
> .../pdb_no_jump.py(8)<module>()
-> if n < 0:

(Pdb) p n
*** NameError: NameError("name 'n' is not defined",)

(Pdb) args

(Pdb)


Вы не можете переходить внутрь таких блоков, как циклы for или выражение try:except.

$ python -m pdb pdb_no_jump.py
> .../pdb_no_jump.py(7)<module>()
-> def f(n):
(Pdb) break 21
Breakpoint 1 at .../pdb_no_jump.py:21

(Pdb) continue
> .../pdb_no_jump.py(21)<module>()
-> print f(5)

(Pdb) jump 26
*** Jump failed: can't jump into the middle of a block

(Pdb)


Код в блоке finally должен быть обязательно выполнен, поэтому вы не можете выпрыгнуть из него.
$ python -m pdb pdb_no_jump.py
> .../pdb_no_jump.py(7)<module>()
-> def f(n):
(Pdb) break 23
Breakpoint 1 at .../pdb_no_jump.py:23

(Pdb) continue
[5, 15, 30, 50, 75]
> .../pdb_no_jump.py(23)<module>()
-> print 'Always printed'

(Pdb) jump 25
*** Jump failed: can't jump into or out of a 'finally' block

(Pdb)


И основное ограничение — переходы ограничены нижним фреймом стека. Если вы перейдёте вверх по стеку для просмотра переменных, вы не сможете изменить ход выполнения в этом уровне.

$ python -m pdb pdb_no_jump.py
> .../pdb_no_jump.py(7)<module>()
-> def f(n):
(Pdb) break 11
Breakpoint 1 at .../pdb_no_jump.py:11

(Pdb) continue
> .../pdb_no_jump.py(11)f()
-> j = 0

(Pdb) where
/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/bdb.py(379)run()
-> exec cmd in globals, locals
<string>(1)<module>()
.../pdb_no_jump.py(21)<module>()
-> print f(5)
> .../pdb_no_jump.py(11)f()
-> j = 0

(Pdb) up
> .../pdb_no_jump.py(21)<module>()
-> print f(5)

(Pdb) jump 25
*** You can only jump within the bottom frame

(Pdb)


Перезапуск программы

Когда отладчик доходит до конца программы, он автоматически запускает её сначала. Вы также можете самостоятельно перезапустить её, не покидая режима отладки, и не теряя брейкпоинтов или других настроек.
  1. #!/usr/bin/env python
  2. # encoding: utf-8
  3. #
  4. # Copyright (c) 2010 Doug Hellmann. All rights reserved.
  5. #
  6.  
  7. import sys
  8.  
  9. def f():
  10.   print 'Command line args:', sys.argv
  11.   return
  12.  
  13. if __name__ == '__main__':
  14.   f()

Запуск вышеприведённой программы в отладчике выведет имя скрипта, так как других аргументов в командной строке передано не было.

$ python -m pdb pdb_run.py
> .../pdb_run.py(7)<module>()
-> import sys
(Pdb) continue

Command line args: ['pdb_run.py']
The program finished and will be restarted
> .../pdb_run.py(7)<module>()
-> import sys

(Pdb)


Можно перезапустить программу с помощью run. Аргументы run парсятся с помощью shlex, и передаются программе, как-будто они из командной строки, так что вы можете перезапускать вашу программу с разными параметрами.

(Pdb) run a b c "this is a long value"
Restarting pdb_run.py with arguments:
a b c this is a long value
> .../pdb_run.py(7)<module>()
-> import sys

(Pdb) continue
Command line args: ['pdb_run.py', 'a', 'b', 'c', 'this is a long value']
The program finished and will be restarted
> .../pdb_run.py(7)<module>()
-> import sys

(Pdb)


run также может быть использована в любом другом месте для перезапуска программы.

$ python -m pdb pdb_run.py
> .../pdb_run.py(7)<module>()
-> import sys
(Pdb) break 10
Breakpoint 1 at .../pdb_run.py:10

(Pdb) continue
> .../pdb_run.py(10)f()
-> print 'Command line args:', sys.argv

(Pdb) run one two three
Restarting pdb_run.py with arguments:
one two three
> .../pdb_run.py(7)<module>()
-> import sys

(Pdb)


Настройка отладчика с помощью алиасов

Есть способ избежать повторный набор сложных команд — определить алиас. Раскрытие алиасов применяется к первому слову каждой команды. Тело алиаса может содержать любую команду, допустимую интерпретатором отладчика, включая команды отладчика и выражения Python. В определениях алиасов допускается рекурсия, так что одни алиасы могут вызывать другие.

$ python -m pdb pdb_function_arguments.py
> .../pdb_function_arguments.py(7)<module>()
-> import pdb
(Pdb) break 10
Breakpoint 1 at .../pdb_function_arguments.py:10

(Pdb) continue
> .../pdb_function_arguments.py(10)recursive_function()
-> if n > 0:

(Pdb) pp locals().keys()
['output', 'n']

(Pdb) alias pl pp locals().keys()

(Pdb) pl
['output', 'n']


Выполнение alias без аргументов отображет список определённых алиасов. С одним аргументом — именем алиаса, выводится его определение.

(Pdb) alias
pl = pp locals().keys()

(Pdb) alias pl
pl = pp locals().keys()
(Pdb)


К аргументам алиаса можно обращаться используя %n, где n — номер аргумента, начиная с 1. Для обращения ко всем аргументам, используйте %*.

$ python -m pdb pdb_function_arguments.py
> .../pdb_function_arguments.py(7)<module>()
-> import pdb
(Pdb) alias ph !help(%1)

(Pdb) ph locals
Help on built-in function locals in module __builtin__:

locals(...)
locals() -> dictionary

Update and return a dictionary containing the current scope's local variables.


Удалите определение алиаса с помощью unalias.

(Pdb) unalias ph

(Pdb) ph locals
*** SyntaxError: invalid syntax (<stdin>, line 1)

(Pdb)


Сохранение настроек конфигурации

Отладка программы требует много повторений; запуск кода, анализ вывода, исправление кода и ввода и снова запуск. pdb старается урезать количество повторений, необходимых для процесса отладки, чтобы позволить вам сконцентрироваться на вашем коде, а не на отладчике. Чтобы помочь уменьшить количество команд, отдаваемых отладчику, pdb позволяет сохранить конфигурацию, чтобы потом снова прочесть её при старте.

Сначала читается файл ~/.pdbrc, позволяя указать личные предпочтения для всех сессий отладки. После этого читается ./.pdbrc из теущего каталога так, что вы можете указывать настройки для конкретного проекта.

$ cat ~/.pdbrc
# Show python help
alias ph !help(%1)
# Overridden alias
alias redefined p 'home definition'

$ cat .pdbrc
# Breakpoints
break 10
# Overridden alias
alias redefined p 'local definition'

$ python -m pdb pdb_function_arguments.py
Breakpoint 1 at .../pdb_function_arguments.py:10
> .../pdb_function_arguments.py(7)<module>()
-> import pdb
(Pdb) alias
ph = !help(%1)
redefined = p 'local definition'

(Pdb) break
Num Type Disp Enb Where
1 breakpoint keep yes at .../pdb_function_arguments.py:10

(Pdb)


Любые команды настройки, которые могут быть введены в шелл отладчика, могут быть сохранены в одном из этих файлов, но не могут команды, управляющие выполнением (continue, jump, ит.д.). Исключение — run, тоесть вы можете устанавливать аргументы командной строки для сессий отладки в ./.pdbrc так, что они будут неизменны на протяжении последующих запусков.
Метки:
Поделиться публикацией
Похожие публикации
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Реклама
Комментарии 21
  • 0
    Раз уж пошла такая пьянка, а нет ли для питона отладчика, понимаемого всякими нетбинсами?
  • +23
    Думал: «Сейчас на ночь что-нибудь интересное небольшое почитаю...»
    • 0
      скажите, плиз, а чем отладчик выигрывает перед логами?
      логи гораздо информативнее, хотя бы потому что ты видишь сразу все значения всех переменных, не нажимая f8 и не следя за изменением значения
      это все равно что сравнить посмотреть через подзорную трубу на что-то или снять этой подзорной трубой панораму и рассматривать большую панораму
      • +3
        В случае с отладичком (я говорю про полноценные GUI отладчики, например как в JetBrains IDEA) я могу при необходимости менять значения переменных и исправлять код методов непосредственно в процессе отладки. То есть по мере выполнения увидел, что в такой-то строчке забыл прибавить еденицу — исправил код и тут же исправил значение переменой, чтобы не запускать всё заново.

        «не нажимая f8 и не следя за изменением значения» — а зачем следить? Есть же условные брэкпойнты.

        Еще насчет «ты видишь сразу все значения всех переменных» — если у вас данных много, то логировать всё вы не сможете, иначе закопаетесь в логах. Отладчик же позволяет получить нужные значения в нужное время с нужной детализацией.

        Если уж сравнивать, то логирование — это статичная фотография, а отладчик — возможность покапаться с микроскопом и пинцетом.

        Да и вообще спор отладчик vs логи имеет мало смысла — всё зависит от задачи и структуры кода, который нужно отладить.
        • +3
          * спор «отладчик vs логи» имеет мало смысла
          • 0
            Ну, почему же. Можно подумать, где лучше применять отладчик, а где отладочную печать.
            Лично я поступаю так, практически всегда применяю отладочную печать.
            В отладчик лезу тогда, когда программа падает, а в каком месте и из-за чего, неизвестно. Вот тогда отладчик, ИМХО, незаменим.
            • +3
              Вот именно, это не спор — просто в некоторых местах удобнее отладчик, в некоторых — логи.
          • 0
            я не говорю выводить в лог значения каждой переменной i
            да и grep-ать логи никто не запрещает
            • +2
              pdb – Интерактивный отладчик

              логи — не интерактивны. читая логи нельзя на ходу повлиять на ход выполнения программы. Отладчик так же позволяет проследить за выполнением программы построчно, тоесть можно походить как по своему коду, так и рассмотреть выполнение внутри 3rd party библиотек (бывает часто полезно с той же джангой).

              Вообще, выше — правильно про спор отладка vs. логи. Это разные инструменты для разных целей
          • 0
            Отладчик удобнее использовать, когда непонятно, куда и почему передается управление.
          • 0
            в принципе есть еще winpdb, гуй на wx, есть возможность удаленной отладки. в принципе лучший отладчик для питона, имхо
            • 0
              pdb хорош. Но не без недостатков (туповатый он, что касается автодополнения и прочих наглядных плюшек).

              Нередко для разбора того, что происходит в коде использую в нужном месте
              import IPython
              ipshell = IPython.core.embed.InteractiveShellEmbed()
              ipshell.mainloop()

              Очень красиво и наглядно получается. :)
              • +3
                $ pip install ipython ipdb

                и потом

                import ipdb; ipdb.set_trace()

                на последнюю команду даже shortcut в редакторе завел.
              • 0
                хотелось бы еще сказать о наличии замечательного плагина для emacs:)
              • 0
                Как можно с помощью pdb отлаживать форки?

                Запускаю pdb, пишу break /path/to/module:10 (после чего в брякпоинтах он отображается, значит добавился успешно), затем cont, выполняется основной процесс, запускает форки, форки отрабатывают, родитель завершается, отладчик перезапускается со словами «программа завершилась, и я запустился заново»

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