← All posts

Python. Debugging with pdb

A snake touching a person’s nose, illustrating the Python debugger article

pdb is an interactive debugger for Python that lets you navigate through code while your program is running, inspect and change variable values, step through code line by line (including stepping into nested code), set breakpoints, and perform all the other operations you would expect from a debugger.

When I don’t have PuDB handy and can’t install it, or the display in urwid gets garbled, I use pdb.

The pdb debugger looks similar to gdb.

Using the debugger

Start the debugger like this:

python -m pdb script.py
python -m pdb manage.py runserver 8000

Alternatively, you can specify in your code where the debugger should start:

import pdb; pdb.set_trace()

Start post-mortem debugging like this:

import pdb; pdb.pm()

Post-mortem debugging lets you inspect the program state after an unhandled exception.

You can also call scripts from your code through pdb.run/pdb.runcall, but the startup methods above are more popular.

List of debugger commands

  • h(elp) — the debugger displays a list of available commands. Run help <command> to get help on a specific command;
  • q(uit), exit — exit the debugger.

General commands

  • l(ist) [<first> [,<last>]] — print the source code. With no arguments, it displays five lines above and five lines below. You can pass two arguments, “first” and “last”, specifying the range of line numbers to display. If you pass just one argument, it displays five lines above and five lines below the specified line number;

  • p <expression>, pp <expression> — “print” and “pprint”, respectively;

  • a(rgs) — display the function’s arguments;

  • whatis <arg> — display the object’s type;

  • alias [<name> [<command> [<parameter>, <parameter> ...]]] — set an alias for a command, for example:

    
    (Pdb) alias lv locals().keys()
    (Pdb) alias gv pp globals().keys()
    

    Aliases can contain debugger commands, Python statements and calls to other aliases. Run alias without arguments to list the defined aliases, or pass an alias name to display its definition.
    Refer to alias arguments by position, for example %1 for the first argument. Use %* to refer to all arguments. For example:

    
    (Pdb) alias dirs !dir(%1)
    dirs __name__
    
  • unalias <name> — delete a specific alias;

  • run [<args>...] — reload the script with the specified parameters, for example:

    
    (Pdb) run runserver 8001
    Restarting ./manage.py with arguments:
        runserver 8001
    > /home/adw0rd/work/project/manage.py(2)()
    -> import os
    
    (Pdb) c
    Validating models...
    0 errors found
    Django version 1.4.1, using settings 'project.settings'
    Development server is running at http://127.0.0.1:8001/
    Quit the server with CONTROL-C.
    

    This way you don’t leave the session, and if you have breakpoints, for instance, you won’t lose them. There is also a built-in alias, restart, which runs run without arguments;

  • ! — a prefix for working directly with the interpreter, such as calling functions or accessing variables. Everything after the prefix is sent to the interpreter for execution. For example:

    
    # Когда имя уникально можно делать и так:
    test_var = 42
    # Но когда оно совпадает с именем команды, надо так:
    !step = 42
    
    (Pdb) step = 42
    > /home/adw0rd/work/project/manage.py(3)()
    -> import sys
    (Pdb) step
    > /home/adw0rd/work/project/manage.py(5)()
    -> if __name__ == "__main__":
    
    (Pdb) !step = 42
    (Pdb) !step
    42
    
  • w(here) — display information about your current position;
  • s(tep) — “step into”: step into a function call if possible; otherwise, move to the next line of code;
  • n(ext) — “step over”: move to the next line of code;
  • unt(il) — move to the next line of code, ensuring that its line number is greater than the current one. For example, if you are at the end of a loop body but this isn’t the last iteration, you won’t be taken back to the beginning of the loop body. Instead, the entire loop will finish executing and the debugger will stop on the next line after the loop, unlike next;
  • r(eturn) — finish (“step out of”) the current function;
  • u(p) — move up one stack frame;
  • d(own) — move down one stack frame;
  • j(ump) <lineno> — jump to the specified line of code without executing the code between the current position and the specified one. Exceptions are for loops and code in a finally block (since it must be executed). Also, you can only jump within the current frame (i.e., the bottom frame).

Breakpoints

From here on, I’ll just call them “breakpoints”, because that’s what I’m used to calling them.

  • When viewing a listing (the “list” command), lines with breakpoints are marked with a “B” prefix:

    
    (Pdb) b
    Num Type         Disp Enb   Where
    1   breakpoint   keep yes   at /home/adw0rd/work/project/manage.py:5
    (Pdb) list
      1     #!/usr/bin/env python
      2  -> import os
      3     import sys
      4
      5 B   if __name__ == "__main__":
      6         os.environ.setdefault("DJANGO_SETTINGS_MODULE", "project.settings")
      7         from django.core.management import execute_from_command_line
      8         execute_from_command_line(sys.argv)
    [EOF]
    
  • b(reak) ([<file>:]<lineno> | <function>) [<condition>] — set and list breakpoints. The arguments let you specify a file and line number or a function where code execution should stop. For example:

    
    (Pdb) b 5
    Breakpoint 1 at /home/adw0rd/work/project/manage.py:5
    (Pdb) b project/settings.py:10
    Breakpoint 2 at /home/adw0rd/work/project/project/settings.py:10
    (Pdb) b my_function
    Breakpoint 3 at /home/adw0rd/work/project/manage.py:15
    

    To view the currently set breakpoints, use break without arguments:

    
    (Pdb) b
    Num Type         Disp Enb   Where
    1   breakpoint   keep yes   at /home/adw0rd/work/project/manage.py:5
    2   breakpoint   keep yes   at /home/adw0rd/work/project/project/settings.py:10
    3   breakpoint   keep yes   at /home/adw0rd/work/project/manage.py:15
    

    Conditional breakpoints deserve a separate look: the breakpoint will trigger only if the condition is true. For example:

    
    # Брейкпоинт выполнится только если переменная "some_var" будет больше 42
    (Pdb) b my_function, some_var > 42
    Breakpoint 4 at /home/adw0rd/work/project/manage.py:15
    
  • condition <bpnumber> <str_condition> — add a condition to an existing breakpoint, for example:

    
    (Pdb) condition 3 True != False
    
  • tbreak — a temporary breakpoint, deleted after use; it has the same syntax as break;

  • ignore <bpnumber> <count> — ignore a specific breakpoint count times. Passing a count of zero resets the ignore count;

  • c(ont(inue)) — continue (until the first breakpoint or until the program finishes);

  • disable <bpnumber> [<bpnumber> ...] and enable <bpnumber> [<bpnumber> ...] — the first disables a breakpoint but does NOT remove it from the breakpoint list. The second enables the breakpoint again. For example:

    
    (Pdb) break
    Num Type        Disp    Enb     Where
    1   breakpoint  keep    yes     at .../script.py:2
    2   breakpoint  keep    yes     at .../script.py:12
    
    (Pdb) disable 1
    (Pdb) break
    Num Type        Disp    Enb     Where
    1   breakpoint  keep    no      at .../script.py:2
    2   breakpoint  keep    yes     at .../script.py:12
    
    (Pdb) enable 1
    (Pdb) break
    Num Type        Disp    Enb     Where
    1   breakpoint  keep    yes     at .../script.py:2
    2   breakpoint  keep    yes     at .../script.py:12
    
  • cl(ear) (<filename>:<lineno> | [<bpnumber>...]) — delete breakpoint(s). If you pass a breakpoint number, only that breakpoint is deleted; if you pass nothing, all breakpoints are deleted. If you pass a path and line number, all breakpoints set at that location are deleted;

  • commands [<bpnumber>] — define additional actions for a breakpoint, such as displaying local variables and so on. For example:

    
    (Pdb) break
    Num Type        Disp    Enb     Where
    1   breakpoint  keep    yes     at .../script.py:2
    
    (Pdb) commands 1
    (com) p "DEBUG"
    (com) pp locals()
    (com) end
    
    (Pdb) continue
    # Тут идёт печать locals()
    

    To remove these actions, run commands again, but with an empty body:

    
    (Pdb) commands 1
    (com) end
    

Saving configuration settings

To configure pdb, create a file named .pdbrc in the root of your home directory or in the project root. Matching instructions in the project root will override those in the root of your home directory.

Example ~/.pdbrc file:


# Алиас, который позволяет указывать несколько объектов для dir()
#   конечно он нужен только для примера...
alias dirs pp [dir(arg) for arg in "%*".split()] 

Example ~/work/project/.pdbrc file:


# Пример переопределения алиаса:
alias dirs pp "OVERRIDDEN!" 

Integration with Emacs

Many IDEs support pdb, and Emacs is no exception. Just launch it like this:

M+x pdb

You’ll get a pdb console in one pane and a real-time code listing in the other—almost PuDB, but still less convenient.

Debugging Python with pdb in Emacs alongside the source code

What else can I read?

The official documentation page, The Python Debugger.

An older article, Using pdb, the Python Debugger (Django Debugging Series, Part 3), and a video:

Video / external material

And the article on Habr, pdb – An Interactive Debugger.

UPD: Python DEBUGGING Fundamentals.

Comments are closed. Read the original discussion in Russian →