Керівництво по використанню pdb

17 хв. читання

Давайте почнемо з того, що таке pdb. pdb — це модуль зі стандартної бібліотеки Python, що дозволяє:

  • Покрокове виконання коду
  • Встановлення брейкпоінтів (breakpoint, точка переривання)
  • Дослідження трасування стеку (stack trace)
  • Перегляд коду
  • Виконання коду в певному контексті
  • "Посмертне" відлагодження (Post-mortem)

Чому pdb?

Використовувати pdb необовзяково, можна обійтися логуванням чи звичайним print.

Але це не дає достатнього контрою над виконанням. А після дебагу потрібно видалити всі непотрібні print. З логуванням простіше: можна просто перемкнути відображення на потрібний рівень. Але проблема цих підходів в тому, що ви не можете контролювати виконання, ви можете лише спостерігати.

З чого почати

Є декілька способів почати використовувати pdb:

1. Запуск програми під керування дебагеру

Ми можемо запустити скрипт під керуванням дебагеру додавши аргумент -m pdb:

$ python -m pdb script.py
 > /pdb-mupy/script.py(1)<module>()
 -> """I am the first script in this demo"""
 (Pdb)

В цьому режимі пітон зупиниться після першого рядка коду і перенесе вас в дебагер ((Pdb) - це запрошення на введення від pdb). Тут ви можете встановити точки переривання або продовжити виконання.

Слід зауважити, якщо після виконання програми не буде ніяких ексепшенів, то програма запуститься знову в тому ж режимі. Якщо будуть якісь помилки, то програма запуститься в режимі post-mortem.

2. Запуск коду з дебагеру

Замість запуску всього скрипту під керуванням дебагеру, ми можемо запустити лише окремі частини коду використовуючи pdb.run, pdb.runeval, pdb.runcall.

>>> import pdb
>>> import script
>>> pdb.run('script.divide(10, 5)')
> <string>(1)<module>()->None
(Pdb) s   # тут ми можемо виконати будь-як pdb-команду
--Call--
> /pdb-mupy/script.py(6)divide()
-> def divide(numerator, denominator):
(Pdb) n
> /pdb-mupy/script.py(7)divide()
-> return numerator / denominator
(Pdb) p numerator, denominator
(10, 5)
(Pdb) c
>>>

<string>(1)<module>() означає що ми на початку і ніякий код ще не виповнювався. В прикладі вище ми входимо в функцію divide за допомогою команди s (я розкажу про можливі команди детально трохи нижче).

runeval() працює так само як і run(), але ще й повертає результат виконання переданого коду.

runcall() дозволяє виконати будь-який callable-об'єкт замість передачі коду текстом.

>>> pdb.runcall(script.divide, 10, 5)
> /pdb-mupy/script.py(7)divide()
-> return numerator / denominator
(Pdb)

3. Встановлення вбудованого брейкпоінту

Це найпопулярніший спосіб налагодження программ. Просто додайте pdb.set_trace() в тій частині коду, де ви хочете призупинити виконання і викликати дебагер.

4. Post-mortem налагодження

Post-mortem налагодження дозволяє дослідити вже мертву програму, використовуючи її traceback об'єкт. В цьому режимі ми можемо дослідити стан програми на момент її падіння. Але ми нічого не можемо зробити окрім того, як дослідити: ніякого покрокового виконання і суміжних фіч.

Як я писав вище, запуск програми з помилками за допомогою -m pdb перенесе нас в режим post-mortem після появи першої помилки. Іншим способом перейти в цей режим є виклик pdb.pm() чи pdm.post_mortem().

pdb.pm() перенесе нас в режим post-mortem, використовуючи ексепшн, знайдений в sys.last_traceback.

А pdb.post_mortem() приймає необов'язковий аргумент в вигляді об'єкту traceback. Також цей метод може працювати з ексепшеном, що обробляється в даний момент.

>>> import pdb
>>> import script
>>> script.divide(10, 0)
Traceback (most recent call last):
  File "<ipython-input-8-fe270324adad>", line 1, in <module>
    script.divide(10, 0)
  File "script.py", line 7, in divide
    return numerator / denominator
ZeroDivisionError: integer division or modulo by zero

Тепер щоб перевірити стан програми в момент виклику ексепшену достатньо виконати pdb.pm().

>>> pdb.pm()
> /Users/ashwini/work/instamojo/pdb-mupy/script.py(7)divide()
-> return numerator / denominator
(Pdb) args  # Аргументи, передані функції
numerator = 10
denominator = 0

Те ж саме, але з використанням pdb.post_mortem():

>>> import sys
>>> pdb.post_mortem(sys.last_traceback)
> /Users/ashwini/work/instamojo/pdb-mupy/script.py(7)divide()
-> return numerator / denominator
(Pdb)

А ось так можна дослідити поточну помилку:

>>> try:
...     script.divide(10, 0)
... except Exception:
...     pdb.post_mortem()
...
> /Users/ashwini/work/instamojo/pdb-mupy/script.py(7)divide()
-> return numerator / denominator
(Pdb)

Базові команди pdb

Запрошення на введення, що ми бачили раніше ((Pdb)) це власна командна оболонка pdb з власними командами, що значно полегшують дебаг. Зараз ми розглянемо ці команди.

Але спершу слід пояснити умовні позначення цих команд. Круглі дужки означають різні імена для однієї команди: наприклад, c(ont(inue))означає, що ми можемо викликати цю команду, набравши c, cont чи continue. Квадратні дужки позначають необов'язковий аргумент. Якщо аргумент не в квадратних дужках — він обов'язковий.

h(elp) [command]

help або просто h дозволяє побачити довідку по певній команді pdb. Без аргументу вона виводить список доступних команд.

(Pdb) help
EOF bt cont enable jump pp run unt a c continue exit l q s until alias cl d h list quit step up args clear debug help n r tbreak w b commands disable ignore next restart u whatis break condition down j p return unalias where

Отримати довідку по команді args:

(Pdb) help args
a(rgs)
Виводить аргументи поточної функції.

Ця команда дозволить зекономити час, не зазираючи постійно в документацію.

Зауважте! help не може вивести довідку про команду !, так як вона приймає лише валідні Python-імена. Натомість ви можете використовувати help exec.

p & pp

Щоб вивести змінну в процесі дебагу ми можемо використовувати p для звичайного прінту, та pp для форматованого виводу. Звісно, ви можете використати стандартний print, але це не команда pdb.

a(rgs)

Виводить аргументи, передані поточній функції.

>>> pdb.runcall(script.divide, 10 , 15)
> /pdb-mupy/script.py(7)divide()
-> return numerator / denominator
(Pdb) args
numerator = 10
denominator = 15

q(uit)

Завершення дебагу та вихід з pdb.

! statment

Ця команда використовується для виконання Python-коду в дебагері. Без ! можливі помилки, якщо якась частина коду має таку ж назву як команди pdb, тому завжди використовуйте ! для виконання Python-коду.

$ python -m pdb script.py
> /pdb-mupy/script.py(1)<module>()
-> """I am the first script in this demo"""
(Pdb) !c = 2  # Оголошуємо змінну c
(Pdb) p c
2

Без ! pdb подумає, що ми хочемо виконати команду c.

(Pdb) c = 2
2
The program finished and will be restarted:
> /pdb-mupy/script.py(1)<module>()
-> """I am the first script in this demo"""
(Pdb)

run [args ...]

run дозволяє перезапустити програму. Це дуже корисно коли ви хочете запустити програму з іншими аргументами без виходу з дебагера.

$ python -m pdb script.py 10 5
> /pdb-mupy/script.py(1)<module>()
-> """I am the first script in this demo"""
(Pdb) !import sys
(Pdb) p sys.argv
['script.py', '10', '5']

А тепер перезапустимо з іншими аргументами.

(Pdb) run 30 40
Restarting script.py with arguments:
    30 40
> /pdb-mupy/script.py(1)<module>()
-> """I am the first script in this demo"""
(Pdb) !import sys
(Pdb) p sys.argv
['script.py', '30', '40']

l(ist) [first[, last]]

Ця команда використовується для перегляду вихідного коду.

Без аргументів ця команда виводить 11 рядків коду навколо поточного. З одним аргументом вона виводить 11 рядків коду навколо заданого рядка. З двома аргументами вона виводить код в заданому проміжку. Якщо другий аргумент менше першого, то вій приймається як кількість рядків, які потрібно вивести.

$ python -m pdb script.py
> /pdb-mupy/script.py(1)<module>()
-> """I am the first script in this demo"""

11 рядків навколо поточного.

(Pdb) list
  1  -> """I am the first script in this demo"""
  2
  3     import sys
  4
  5
  6     def divide(numerator, denominator):
  7         return numerator / denominator
  8
  9
 10     if __name__ == '__main__':
 11         numerator = int(sys.argv[1])

Відображення коду з пятого по восьмий рядок.

(Pdb) list 5, 8
  5
  6     def divide(numerator, denominator):
  7         return numerator / denominator
  8

alias [name [command]] та unalias

alias використовується для встановлення скорочень для команд. unalias для видалення вже створених скорочень.

Давайте створимо аліас, що буде повертати квадрати чисел:

(Pdb) alias squares [i**2 for i in xrange(%1)]
(Pdb) squares 5
[0, 1, 4, 9, 16]

Тут %1 - аргумент, що передається при виконанні аліасу (в даному випадку 5). Більше того, ви можете використовувати %2, %3 і так далі.

Також можна встановити аліас для аліасу:

(Pdb) alias squares_7 squares 7

Тепер squares_7 те ж саме, що й squares 7.

(Pdb) squares_7
[0, 1, 4, 9, 16, 25, 36]

Щоб видалити аліас використовуйте unalias з іменем аліасу в якості параметру.

Покрокове виконання коду

Однією з найпотужніших можливостей pdb є різні способи покрокового виконання коду.

  • Рядок за рядком
  • Перехід всередину функції
  • Пропуск циклу
  • Пропуск функції

В цій частині статті ми дізнаємося більше про команди, що дозволяють виконувати код. Ми будемо працювати з кодом в файлі next_and_step_until.py. А ось команди, які ми будемо використовувати. Вони схожі між собою, тому слід чітко відрізняти одну від одної.

n(ext)

Ця команда виконує код в поточному рядку на повній швидкості і переміщає нас на наступний рядок в поточній функції.

s(tep)

Ця команда схожа на попередню, але при виклику callable (наприклад, функції) вона перенесе нас всередину цього об'єкту, а не на наступний рядок. В іншому ж поведінка ідентична до next.

unt(il)

until вказує дебагеру продовжувати виконання доки номер рядка буде не більше поточного. Дуже зручно при пропуску циклів.

r(eturn)

return переміщає нас до кінця поточної функції. На глобальному рівні — до кінця поточного модуля. Ця команда зручна коли ви хочете виконати тіло функції за раз, а не покроково.

c(ont(inue))

Ця команда запускає весь код на повній швидкості до наступної точки переривання, якщо така є.

Давайте дослідимо наш скрипт з використанням вищеподаних команд. У нас стоять брейкопінти на дев'ятнадцятому рядку, тому дебагер зупиниться на наступному непустому рядку (двадцять перший).

$ python next_and_step_until.py
> /Users/ashwini/work/instamojo/pdb-mupy/next_and_step_until.py(21)<module>()
-> knights() 
(Pdb)

Тепер для запуску функції knights() використаємо n(ext). Як бачите, воно надрукувало текст, який був всередині функції та зупинилося на наступному рядку поточного коду.

(Pdb) n
We are the Knights who say ni!
Get us a shrubbery.
> /Users/ashwini/work/instamojo/pdb-mupy/next_and_step_until.py(22)<module>()
-> credits()  # Want to step inside this function? Use s(tep).
(Pdb)

Тепер уявимо, що нам потрібно віддебажити щось у виклику функції credits(). Для цього скористаємося командою s(tep).

(Pdb) s
--Call--
> /pdb-mupy/next_and_step_until.py(11)credits()
-> def credits():
(Pdb) n
> /pdb-mupy/next_and_step_until.py(12)credits()
-> print "A Møøse once bit my sister... No realli!"
(Pdb) n
A Møøse once bit my sister... No realli!
> /pdb-mupy/next_and_step_until.py(13)credits()
-> print "We apologise for the fault in the print statements."
(Pdb)

Тепер, коли ми закінчили з цією функцією, можемо викликати r(eturn) щоб перейти в її кінець, а потім n щоб вийти:

(Pdb) r
We apologise for the fault in the print statements.
Those responsible have been sacked.
--Return--
> /Users/ashwini/work/instamojo/pdb-mupy/next_and_step_until.py(15)credits()->'M\\xc3\\xb8\\xc...pretti nasti.'
-> return "Møøse bites Kan be pretti nasti."
(Pdb) n
> /Users/ashwini/work/instamojo/pdb-mupy/next_and_step_until.py(24)<module>()
-> for i in range(1, 5):

Тепер ми всередині циклу, і ні next, ні step не може пропустити його в один крок. Щоб пропустити цикл, перейдемо до його останнього рядка і викличемо until.

-> for i in range(1, 5):
(Pdb) n
> /pdb-mupy/next_and_step_until.py(26)<module>()
-> print "Shrubbery #{}".format(i)
(Pdb) until
Shrubbery #1
Shrubbery #2
Shrubbery #3
Shrubbery #4
> /pdb-mupy/next_and_step_until.py(28)<module>()
-> print "We have found the Holy Grail."
(Pdb)

Тепер ми можемо продовжити виконання програми, викликавши continue.

(Pdb) cont
We have found the Holy Grail.

Переміщення між стеками

Ми розглянули як переміщатися по коду, але pdb дозволяє також переміщатися по поточному стеку.

Краще всього пояснити це можна на прикладі рекурсивної функції (recursive.py).

Є три базові команди, які ми розглянемо в цій частині статті. Це u(p), d(own) та w(here).

w(here)

Ця команда друкує весь стек викликів від першого до поточного.

$ python recursive.py
> /pdb-mupy/recursive.py(10)func()
-> return 0
(Pdb) where
  /pdb-mupy/recursive.py(14)<module>()
-> print (func(4))
  /pdb-mupy/recursive.py(7)func()
-> return func(n - 1)
  /pdb-mupy/recursive.py(7)func()
-> return func(n - 1)
  /pdb-mupy/recursive.py(7)func()
-> return func(n - 1)
  /pdb-mupy/recursive.py(7)func()
-> return func(n - 1)
> /pdb-mupy/recursive.py(10)func()  # Current frame
-> return 0
(Pdb)

> позначає поточний рівень.

Тепер ми можемо переміщуватися по стеку вверх та вниз. Давайте перемістимося на два виклики вверх та переглянемо аргументи функції. Брейкпоінт був встановлений при значенні n = 0, після нашого переміщення n має дорівнювати 2.

(Pdb) u
> /pdb-mupy/recursive.py(7)func()
-> return func(n - 1)
(Pdb) u
> /pdb-mupy/recursive.py(7)func()
-> return func(n - 1)
(Pdb) args
n = 2
(Pdb)

Так само можна спуститися вниз.

(Pdb) d
> /pdb-mupy/recursive.py(7)func()
-> return func(n - 1)
(Pdb) args
n = 1

up також можна використовувати якщо ви ввійшли в функцію (step), та хочете вийти.

Брейпоінти

Раніше ми навчилися встановлювати точки переривання в коді за допомогою встановлення pdb.set_trace() в тому місці, де ми хочемо призупинити програму. Але pdb дозволяє встановлювати брейкпоінти динамічно, без зміни сирцевого коду. Розглядати будемо на прикладі breakpoints.py)

b(reak) [[filename:]lineno | function[, condition]]

Ми можемо встановити брейкпоінт в поточному файлі вказанням конкретного рядка або функції. Також ми можемо встановити брейкпоінт в іншому файлі (але він повинен бути доступний для імпорту) вказавши ім'я файлу та номер рядка.

Кожен брейкпоінт має свій номер, за яким до нього потім можна звертатися.

condition — це Python-вираз. Якщо він істинний, брейкпоінт спрацьовує. Цей вираз виконується в контексті, де ми хочемо встановити точку переривання.

Декілька прикладів використання:

  • break 13 # Встановити брейкпоінт на 13-ому рядку
  • break divide # Встановлення брейпоінту на функції divide
  • break divide, denominator == 0 # Встановлення брейкпоінту на функції divide, але тільки якщо denominator == 0
$ python -m pdb breakpoints.py 10 0
> /pdb-mupy/breakpoints.py(2)<module>()
-> import sys
(Pdb) break divide, denominator == 0
Breakpoint 1 at /pdb-mupy/breakpoints.py:5

Отримати список точок переривання можна виконавши break без аргументів. В отриманому списку Num це номер точки, Disp == keep означає що це постійний брейкпоінт, а End = yes що він зараз ввімкнений.

(Pdb) break
    Num Type         Disp Enb   Where
    1   breakpoint   keep yes   at /pdb-mupy/breakpoints.py:5
        stop only if denominator == 0
    (Pdb) c

Наш брейкпоінт спрацював, і програма призупинилася.

> /pdb-mupy/breakpoints.py(8)divide()
    -> print "Calculating {}/{}".format(numerator, denominator)
    (Pdb) args
    numerator = 10.0
    denominator = 0.0

Давайте перезапустимо її з іншими аргументами.

(Pdb) run 10 5
Restarting breakpoints.py with arguments:
    10 5
> /pdb-mupy/breakpoints.py(2)<module>()
-> import sys
(Pdb) c
Calculating 10.0/5.0
2.0
The program finished and will be restarted
> /pdb-mupy/breakpoints.py(2)<module>()
-> import sys

Так як denominator != 0, програма виконалася без зупинок.

(Pdb) break
Num Type         Disp Enb   Where
1   breakpoint   keep yes   at /pdb-mupy/breakpoints.py:5
    stop only if denominator == 0
    breakpoint already hit 2 times

Брейкпоінти зберігаються навіть після автоматичного або насильного перезапуску (run) програми, якщо він був запущений за допомогою -m pdb

tbreak [[filename:]lineno | function[, condition]]

tbreak дозволяє встановлювати тимчасові точки переривання. Такі брейкпоінти видаляються після того, як спрацюють хоч раз. Дуже зручно використовувати такі всередині циклу.

$ python -m pdb breakpoints.py 10 5
> /pdb-mupy/breakpoints.py(2)<module>()
-> import sys
(Pdb) tbreak divide, denominator == 0
Breakpoint 1 at/pdb-mupy/breakpoints.py:5
(Pdb) break
Num Type         Disp Enb   Where
1   breakpoint   del  yes   at /pdb-mupy/breakpoints.py:5
    stop only if denominator == 0

Зауважте, тепер Disp=del, що означає, що це тимчасова точка.

(Pdb) c
Calculating 10.0/5.0
2.0
The program finished and will be restarted
> /Users/ashwini/work/instamojo/pdb-mupy/breakpoints.py(2)<module>()
-> import sys

Програма перезапустилася, так як не спрацював вираз всередині брейкпоінту. Давайте перезапустимо скрипт з потрібними аргументами.

(Pdb) run 10 0
Restarting breakpoints.py with arguments:
    10 0
> /pdb-mupy/breakpoints.py(2)<module>()
-> import sys
(Pdb) c
Deleted breakpoint 1
> /Users/ashwini/work/instamojo/pdb-mupy/breakpoints.py(8)divide()
-> print "Calculating {}/{}".format(numerator, denominator)
(Pdb) break
(Pdb)

Тепер він спрацює і видалиться після.

cl(ear) [filename:lineno | bpnumber [bpnumber ...]]

За допомогою цієї команди ми можемо видалити постійні брейкпоінти.

disable [bpnumber [bpnumber ...]] or enable [bpnumber [bpnumber ...]]

Щоб тимчасово вимкнути брейкпоінт слід скористатися командою disable, а щоб ввімкнути — enable. На відміну від clear, вона не видаляє їх, а лише робить неактивними.

ignore bpnumber [count]

За допомогою цієї команди ми можемо ігнорувати брейкпоінт задану кількість разів.

condition bpnumber [condition]

Щоб оновити або додати вираз до точки переривання слід скористатися цією командою.

commands [bpnumber]

commands це дуже корисна команда, повязана з точками переривання. Використана певним чином, вона може замінити використання print. Вона використовується для виконання декількох команд при спрацюванні брейкпоінту.

Давайте додамо брейкпоінт до функції divide і будемо друкувати текст при його спрацюванні.

$ python -m pdb breakpoints.py 10 5
> /pdb-mupy/breakpoints.py(2)<module>()
-> import sys
(Pdb) break divide
Breakpoint 1 at /Users/ashwini/work/instamojo/pdb-mupy/breakpoints.py:5

В режимі commands запрошення на ввід стає (com), щоб завершити введіть end.

(Pdb) commands 1
(com) args
(com) p "Inside divide()"
(com) end
(Pdb) c
numerator = 10.0
denominator = 5.0
'Inside divide()'
> /pdb-mupy/breakpoints.py(8)divide()
-> print "Calculating {}/{}".format(numerator, denominator)

Іншою командою є silent, що заглушає весь вивід.

$ python -m pdb breakpoints.py 10 5
> /pdb-mupy/breakpoints.py(2)<module>()
-> import sys
(Pdb) break divide
Breakpoint 1 at /pdb-mupy/breakpoints.py:5
(Pdb) commands 1
(com) args
(com) p "Inside divide()"
(com) silent
(com) cont
(Pdb) c
numerator = 10.0
denominator = 5.0
'Inside divide()'
Calculating 10.0/5.0
2.0
The program finished and will be restarted
> /pdb-mupy/breakpoints.py(2)<module>()
-> import sys
(Pdb)

Маленькі хитрощі

  • Якщо в поточній та/або домашній директорії є файл .pdbrc, то перед кожною сесією дебагу будуть виконуватися команди, записані в цьому файлі.

  • Ввід пустої команди повторює попередню (list — виключення).

  • Щоб ввести відразу декілька команд, використовуйте ;;.

Що нового в Python3

Python 3.3+

  • Автодоповнення по Tab. Як для команд, так і для аргументів.

Python 3.2+

  • pdb.py приймає аргумент -c, що еквівалентний файлу .pdbrc.

  • Нова команда ll, що дозволяє переглянути код, пов'язаний з поточною функцією або рівнем стеку.

  • Нова команда source дозволяє подивитися код якогось виразу: source expression

  • Нова команда interact запускає інтерактивний режим. В Python2 можна викликати за допомогою !import code; code.interact(local=vars()).

Помітили помилку? Повідомте автору, для цього достатньо виділити текст з помилкою та натиснути Ctrl+Enter
Codeguida 5.8K
Приєднався: 8 місяців тому
Коментарі (0)

    Ще немає коментарів

Щоб залишити коментар необхідно авторизуватися.

Вхід / Реєстрація