Pdb like a Pro
Master the basics and advanced usages of pdb to increase your productivity.
If you live within your shell
(pun intended), you are ought to know about the oldest kid in the block, the pdb (Python Debugger), which is almost the counterpart of gdb (GNU Debugger) inC/C++
.
Pdb is most powerful debugger you can imagine and mastering it will revolutionize your development skills
If you liked pdb, you’ll love pdb++. The pdb++ debugger extends the basic functionalities of pdb with some nice to have features, the most important of which is the syntax highlighting. At the time of writing this post, the latest versio of pdb++ was 0.10.3
.
This tutorial focuses on Python 3.7+ as pdb got some enhancements in versions 3.2 and 3.7. We will use the termspdb
and pdb++
interchangeably assuming that pdb++
is installed and is the default debugger for your Python environment.
Table of Contents
· How to install pdb++?
· Basics Usecases of pdb
∘ Set Breakpoint
∘ Disable All Breakpoints
∘ Post-mortem Debugging
· Pdb and Pdb++ Commands
∘ Navigate Source Code
∘ Exploring the Context
∘ Moving between stacks
· Advance Breakpoints
∘ Breakpoint @ line
∘ List all breakpoints
∘ Clear all breakpoints
∘ Breakpoint by module (file)
∘ Breakpoint by function
∘ Breakpoint by condition
∘ Temporary Breakpoint (tbreak)
∘ Code Execution
How to install pdb++?
You can install pdb++
via pip or conda package managers (the latest version at the time of this post was 0.10.3):
pip install pdbpp # install via pip
conda install -c conda-forge pdbpp # install via conda
The pdb++
will automatically be used in all places that pdb
was used. Since pdb++
is only a wrapper over pdb
, the conventional pdb
module is still available as import pdb; pdb.pdb
.
Basics Usecases of pdb
Set Breakpoint
In Python 3.7+, a breakpoint can be set by calling the built-in breakpoint()
function. The execution of the python module will pause at the breakpoint()
line and pdb
will be launched.
In older versions of python, breakpoints were created by calling the set_trace()
function of the pdb module ( from pdb import set_trace; set_trace
).
Note: Learn about advanced usages of breakpoint and the b[reakpoint] command in the Advanced Breakpoint secion.
Disable All Breakpoints
Setting the PYTHONBREAKPOINT
environment variable to 0
will disable all the breakpoints in the code, which is a lot easier than manually commenting them in the code. This is a sweet feature when disabling a handful of breakpoints during debugging.
python -c "breakpoint()"
# Will pause execution at the breakpoint and start the debugger
PYTHONBREAKPOINT=0 python -c "breakpoint()"
# Skips the breakpoint
Post-mortem Debugging
The pdb
can also be invoked as a script to debug other scripts, which will automatically enter the post-mortem debugging. With post-mortem debugging, pdb
is invoked upon an uncaught exception. This is particularly helpful when debugging 3rd party libraries where inserting breakpoints in the source code is not as straightforward.
python -m pdb foo.py
This will pause at the very beginning of the execution and await a continue
(c
or cont
) command from the user (see Pdb Commands section). To skip the initial pause, run with -c continue
flag:
python -m pdb -c continue foo.py
Advance Note: In Python 3.7+, pdb
accepts the -m
flag; so, the bar
module within the foo
package can be invoked with:
python -m foo.bar
python -m pdb -m foo.bar
Bonus: To learn about -m
flag in Python, read this response on StackOverflow.
Pdb and Pdb++ Commands
When pdb
starts, the developer can start inspecting the state of the execution. To get the list of available commands, try help
or h
. Most commands can be abbreviated to one or two letters as indicated; e.g., h(elp)
or cl(ear)
. Both forms are equivalent.
A detailed description of all debugger commands is available in the pdb
references: https://docs.python.org/3/library/pdb.html. Here, we will discuss the most useful ones.
The functionality of the commands is demonstrated with the bar.py
code snippet. Notice the breakpoint()
in func3.
# foo/bar.py
def func3(sky, ocean, sea):
breakpoint()
print('func1', sky, ocean, sea)
def func2(sky, ocean):
# This is a long docstring to enable scrolling.
#
#
#
#
# end of docstring
func3(sky, ocean, 3)
def func1(sky):
func2(sky, 2)
func1(1)
Navigate Source Code
l[ist]: Lists 11 lines around the current line or continues the previous listing. The ->
indicator points to where execution is paused. List is not an idempotent function and iteratively fetches the next 11 lines of the module.
✦ ❯ python foo/bar.py
[3] > /Users/amirabdi/pdb_like_a_pro/foo/bar.py(5)func3()
-> print('func1', sky, ocean, sea)
(Pdb++) l
1 # foo/bar.py
2
3 def func3(sky, ocean, sea):
4 breakpoint()
5 -> print('func1', sky, ocean, sea)
6
7
8 def func2(sky, ocean):
9 # This is a long docstring to enable scrolling.
10 #
11 #
(Pdb++) l
12 #
13 #
14 # end of docstring
15 func3(sky, ocean, 3)
16
17
18 def func1(sky):
19 func2(sky, 2)
20
21
22 func1(1)
(Pdb++) l
[EOF]
Advance Note: To reset the iterator back to the beginning, try: l .
(Pdb++) l.
1 # foo/bar.py
2
3 def func3(sky, ocean, sea):
4 breakpoint()
5 -> print('func1', sky, ocean, sea)
6
7
8 def func2(sky, ocean):
9 # This is a long docstring to enable scrolling.
10 #
11 #
ll (long list): The idempotent and smarter alternative to l[ist] which shows the lines of the active function.
(Pdb++) ll
3 def func3(sky, ocean, sea):
4 breakpoint()
5 -> print('func1', sky, ocean, sea)
(Pdb++) ll
3 def func3(sky, ocean, sea):
4 breakpoint()
5 -> print('func1', sky, ocean, sea)
source: The source
command can be used to get the source code of a module, class, method, function, traceback, frame, or code.
Imagine the following class definition in the foo.my_class
module:
Make sure to import the module/class before asking to view its source:
(Pdb++) from foo.my_class import MyClass
(Pdb++) source MyClass
3 class MyClass:
4 """A delicate class indeed!"""
5 pass
The source
command can also fetch the source code of objects from other packages.
(Pdb++) import numpy
(Pdb++) source numpy
0 """
1 NumPy
2 =====
3
4 Provides
5 1. An array object of arbitrary homogeneous items
6 2. Fast mathematical operations over arrays
7 3. Linear Algebra, Fourier Transforms, Random Number Generation
8
9 How to use the documentation
10 ----------------------------
11 Documentation is available in two forms: docstrings provided
12 with the code, and a loose standing reference guide, available from
13 `the NumPy homepage <https://www.scipy.org>`_.
14
15 We recommend exploring the docstrings using
16 `IPython <https://ipython.org>`_, an advanced Python shell with
17 TAB-completion and introspection capabilities. See below for further
18 instructions.
Exploring the Context
locals(): While this is not a pdb
command, the built-in python locals()
function is a helpful debugging tool to get the current local symbol table.
(Pdb++) ll
3 def func3(sky, ocean, sea):
4 breakpoint()
5 -> print('func1', sky, ocean, sea)
(Pdb++) locals()
{'sky': 1, 'ocean': 2, 'sea': 3}
Expressions and Statements: Any python expression and statement is valid in pdb
, including assignments.
(Pdb++) locals()
{'sky': 1, 'ocean': 2, 'sea': 3}
(Pdb++) my_var = sum([v for k,v in locals().items()])
(Pdb++) locals()
{'sky': 1, 'ocean': 2, 'sea': 3, 'my_var': 6}
? (inspect): To learn more about a variable, use ?
after its name (e.g. my_var?
), or use inspect
(e.g. inspect my_var
).
(Pdb++) sky
1
(Pdb++) sky?
Type: int
String Form: 1
Docstring: int([x]) -> integer
int(x, base=10) -> integer
Convert a number or string to an integer, or return 0 if no arguments
are given. If x is a number, return x.__int__(). For floating point
numbers, this truncates towards zero.
If x is not a number or if base is given, then x must be a string,
bytes, or bytearray instance representing an integer literal in the
given base. The literal can be preceded by '+' or '-' and be surrounded
by whitespace. The base defaults to 10. Valid bases are 0 and 2-36.
Base 0 means to interpret the base from the string as an integer literal.
>>> int('0b100', base=0)
4
a[rgs]: Print the argument list of the current function.
(Pdb++) ll
3 def func3(sky, ocean, sea):
4 breakpoint()
5 -> print('func1', sky, ocean, sea)
(Pdb++) args
sky = 1
ocean = 2
sea = 3
(Pdb++)
pp: The pp
does what it says, pretty printing, and is mainly helpful when dealing with big containers.
(Pdb++) {i:i for i in range(1000, 1020)}
{1000: 1000, 1001: 1001, 1002: 1002, 1003: 1003, 1004: 1004, 1005: 1005, 1006: 1006, 1007: 1007, 1008: 1008, 1009: 1009, 1010: 1010, 1011: 1011, 1012: 1012, 1013: 1013, 1014: 1014, 1015: 1015, 1016: 1016, 1017: 1017, 1018: 1018, 1019: 1019}
(Pdb++) pp {i:i for i in range(1000, 1020)}
{1000: 1000,
1001: 1001,
1002: 1002,
1003: 1003,
1004: 1004,
1005: 1005,
1006: 1006,
1007: 1007,
1008: 1008,
1009: 1009,
1010: 1010,
1011: 1011,
1012: 1012,
1013: 1013,
1014: 1014,
1015: 1015,
1016: 1016,
1017: 1017,
1018: 1018,
1019: 1019}
(Pdb++)
Moving between stacks
w[here]: Print a stack, with the most recent frame at the bottom. A >
symbol indicates the current frame.
(Pdb++) w
[0] /Users/amirabdi/pdb_like_a_pro/foo/bar.py(22)<module>()
-> func1(1)
[1] /Users/amirabdi/pdb_like_a_pro/foo/bar.py(19)func1()
-> func2(sky, 2)
[2] /Users/amirabdi/pdb_like_a_pro/foo/bar.py(15)func2()
-> func3(sky, ocean, 3)
[3] > /Users/amirabdi/pdb_like_a_pro/foo/bar.py(5)func3()
-> print('func1', sky, ocean, sea)
The above stack trace indicates that we are currently in func3()
at line 3 of the bar.py
module and the next line to be executed is print(...)
.
u[p]: Move up one frame.
(Pdb++) l .
1 # foo/bar.py
2
3 def func3(sky, ocean, sea):
4 breakpoint()
5 -> print('func1', sky, ocean, sea)
6
7
8 def func2(sky, ocean):
9 func3(sky, ocean, 3)
10
11
(Pdb++) u
[2] > /Users/amirabdi/pdb_like_a_pro/foo/bar.py(9)func2()
-> func3(sky, ocean, 3)
(Pdb++) l .
4 breakpoint()
5 print('func1', sky, ocean, sea)
6
7
8 def func2(sky, ocean):
9 -> func3(sky, ocean, 3)
10
11
12 def func1(sky):
13 func2(sky, 2)
d[own]: Move down one frame.
(Pdb++) l .
4 breakpoint()
5 print('func1', sky, ocean, sea)
6
7
8 def func2(sky, ocean):
9 -> func3(sky, ocean, 3)
10
11
12 def func1(sky):
13 func2(sky, 2)
14
(Pdb++) d
[3] > /Users/amirabdi/pdb_like_a_pro/foo/bar.py(5)func3()
-> print('func1', sky, ocean, sea)
(Pdb++) l .
1 # foo/bar.py
2
3 def func3(sky, ocean, sea):
4 breakpoint()
5 -> print('func1', sky, ocean, sea)
6
7
8 def func2(sky, ocean):
9 func3(sky, ocean, 3)
f[rame] <NUM>: Jump to frame number indicated. Example f 3
jumps to frame number 3
.
(Pdb++) f 0
[0] > /Users/amirabdi/pdb_like_a_pro/foo/bar.py(16)<module>()
-> func1(1)
(Pdb++) l .
11
12 def func1(sky):
13 func2(sky, 2)
14
15
16 -> func1(1)
[EOF]
(Pdb++) f 3
[3] > /Users/amirabdi/pdb_like_a_pro/foo/bar.py(5)func3()
-> print('func1', sky, ocean, sea)
(Pdb++) l .
1 # foo/bar.py
2
3 def func3(sky, ocean, sea):
4 breakpoint()
5 -> print('func1', sky, ocean, sea)
6
7
8 def func2(sky, ocean):
9 func3(sky, ocean, 3)
Advance Breakpoints
You can set a breakpoint using the b[reakpoint] pdb command. It’s a very powerful tool; mastering the breakpoint skill gets you very far in life!
Breakpoint @ line
A breakpoint can be set at a line of the current module using the b
command such as b <line_number>
.
(Pdb++) b 9
Breakpoint 1 at /Users/amirabdi/pdb_like_a_pro/foo/bar.py:9
(Pdb++) b 13
Breakpoint 2 at /Users/amirabdi/pdb_like_a_pro/foo/bar.py:13
When a new breakpoint is set, pdb confirms.
List all breakpoints
The b
command without any arguments lists all breakpoints, excluding the breakpoints you hard-coded in the source code using breakpoint()
.
(Pdb++) b
Num Type Disp Enb Where
1 breakpoint keep yes at /Users/amirabdi/pdb_like_a_pro/foo/bar.py:14
breakpoint already hit 1 time
2 breakpoint keep yes at /Users/amirabdi/pdb_like_a_pro/foo/bar.py:9
Here, pdb is letting you know that b in line 14 has been “hit” once.
Clear all breakpoints
The cl[ear] command deletes breakpoints:
cl
: deletes all breakpoints from all modules
(Pdb++) b
Num Type Disp Enb Where
1 breakpoint keep yes at /Users/amirabdi/pdb_like_a_pro/foo/bar.py:14
breakpoint already hit 1 time
2 breakpoint keep yes at /Users/amirabdi/pdb_like_a_pro/foo/bar.py:9
(Pdb++) cl
Clear all breaks? yes
Deleted breakpoint 1 at /Users/amirabdi/pdb_like_a_pro/foo/bar.py:14
Deleted breakpoint 2 at /Users/amirabdi/pdb_like_a_pro/foo/bar.py:9
(Pdb++) b
(Pdb++)
cl <breakpoint_number>
: deletes the indicated breakpoint
(Pdb++) b
Num Type Disp Enb Where
3 breakpoint keep yes at /Users/amirabdi/pdb_like_a_pro/foo/bar.py:14
4 breakpoint keep yes at /Users/amirabdi/pdb_like_a_pro/foo/bar.py:9
(Pdb++) cl 3
Deleted breakpoint 3 at /Users/amirabdi/pdb_like_a_pro/foo/bar.py:14
(Pdb++) b
Num Type Disp Enb Where
4 breakpoint keep yes at /Users/amirabdi/pdb_like_a_pro/foo/bar.py:9
Breakpoint by module (file)
Breakpoints are set to the current module by default. To set a breakpoint on a different file, use the module path. For example,
(Pdb++) b /path/to/foo.py:10
sets a breakpoint at line 10 of the foo.py
file.
Breakpoint by function
The b <function_name>
command will set a breakpoint at the very first executable line of the given function. The function should have already been imported in the current scope and it can be from a 3rd party library.
In the example below, the b func2
command sets a breakpoint on line 7 which stops the execution at line 16 of the bar.py
module.
# run in post-mortem to pause at the beginning
✦ ❯ python -m pdb foo/bar.py
[2] > /Users/amirabdi/pdb_like_a_pro/foo/bar.py(3)<module>()
-> def func3(sky, ocean, sea):
(Pdb++) l .
3 -> def func3(sky, ocean, sea):
4 print('func1', sky, ocean, sea)
5
6
7 def func2(sky, ocean):
8 func3(sky, ocean, 3)
9
10
11 def func1(sky):
(Pdb++) b func2
Breakpoint 1 at /Users/amirabdi/pdb_like_a_pro/foo/bar.py:7
(Pdb++) cont
[4] > /Users/amirabdi/pdb_like_a_pro/foo/bar.py(8)func2()
-> func3(sky, ocean, 3)
(Pdb++) l .
3 def func3(sky, ocean, sea):
4 print('func1', sky, ocean, sea)
5
6
7 B def func2(sky, ocean):
8 -> func3(sky, ocean, 3)
9
10
11 def func1(sky):
12 func2(sky, 2)
This approach can be even used to set breakpoints on functions of 3rd party libraries. For example, the following script is using the requests
library:
# call.py
import requests
breakpoint()
requests.get('https://google.com')
The developer can set a breakpoint at the beginning of the get
function with the b requests.get
command and inspect it as follows.
✦ ❯ python call.py
[0] > /Users/amirabdi/pdb_like_a_pro/call.py(4)<module>()
-> requests.get('https://google.com')
(Pdb++) b requests.get
Breakpoint 1 at /Users/amirabdi/miniconda3/lib/python3.7/site-packages/requests/api.py:64
(Pdb++) cont
[1] > /Users/amirabdi/miniconda3/lib/python3.7/site-packages/requests/api.py(75)get()
-> kwargs.setdefault('allow_redirects', True)
(Pdb++) l
70 :param \*\*kwargs: Optional arguments that ``request`` takes.
71 :return: :class:`Response <Response>` object
72 :rtype: requests.Response
73 """
74
75 -> kwargs.setdefault('allow_redirects', True)
76 return request('get', url, params=params, **kwargs)
77
78
79 def options(url, **kwargs):
80 r"""Sends an OPTIONS request.
See how we take advantage of the ll
command to inspect the source code.
Breakpoint by condition
Conditional breakpoints are set with the b <where>, <condition>
command and triggered only when the condition is met.
Imagine the following recursive fibonacci function:
# fibonnaci.py
def recur_fibo(value):
if value <= 1:
return value
else:
return(recur_fibo(value-1) + recur_fibo(value-2))
recur_fibo(10)
A breakpoint can be set conditioned on value == 5
as and the conditions are displayed within the list of breakpoints:
✦ ❯ python -m pdb fibonacci.py
[2] > /Users/amirabdi/pdb_like_a_pro/fibonacci.py(2)<module>()
-> def recur_fibo(value):
(Pdb++) b recur_fibo, value == 5
Breakpoint 1 at /Users/amirabdi/pdb_like_a_pro/fibonacci.py:2
(Pdb++) b
Num Type Disp Enb Where
1 breakpoint keep yes at /Users/amirabdi/pdb_like_a_pro/fibonacci.py:2
stop only if value == 5
If the execution is continued (cont
), it will stop only when the condition is met:
✦ ❯ python -m pdb fibonacci.py
[2] > /Users/amirabdi/pdb_like_a_pro/fibonacci.py(2)<module>()
-> def recur_fibo(value):
(Pdb++) b recur_fibo, value == 5
Breakpoint 1 at /Users/amirabdi/pdb_like_a_pro/fibonacci.py:2
(Pdb++) b
Num Type Disp Enb Where
1 breakpoint keep yes at /Users/amirabdi/pdb_like_a_pro/fibonacci.py:2
stop only if value == 5
(Pdb++) cont
[8] > /Users/amirabdi/pdb_like_a_pro/fibonacci.py(3)recur_fibo()
-> if value <= 1:
(Pdb++) l .
1 # fibonnaci.py
2 B def recur_fibo(value):
3 -> if value <= 1:
4 return value
5 else:
6 return(recur_fibo(value-1) + recur_fibo(value-2))
7
8 recur_fibo(10)
[EOF]
(Pdb++) value
5
Advanced Question: In recur_fibo(10)
, and conditional breakpoint b recur_fibo, value == 5
, how many times will the execution be paused at the specified breakpoint?
Answer: 8
Temporary Breakpoint (tbreak)
A temporary breakpoint is set with the tbreak
command and is breaks the execution only once.
In the above recursive fibonacci example, the breakpoint set with the commandtbreak recur_fibo
and is deleted automatically right after the first hit:
~/pdb_like_a_pro via 🐍 v3.7.4 via C base on ☁️ us-west-2 took 3m37s
✦ ❯ python -m pdb fibonacci.py
[2] > /Users/amirabdi/pdb_like_a_pro/fibonacci.py(2)<module>()
-> def recur_fibo(value):
(Pdb++) tbreak recur_fibo
Breakpoint 1 at /Users/amirabdi/pdb_like_a_pro/fibonacci.py:2
(Pdb++) cont
Deleted breakpoint 1 at /Users/amirabdi/pdb_like_a_pro/fibonacci.py:2
[3] > /Users/amirabdi/pdb_like_a_pro/fibonacci.py(3)recur_fibo()
-> if value <= 1:
(Pdb++) value
10
(Pdb++) cont
The program finished and will be restarted
Temporary breakpoints are marked as del
when breakpoints are listed:
(Pdb++) tbreak 5
Breakpoint 1 at /Users/amirabdi/pdb_like_a_pro/fibonacci.py:5
(Pdb++) break 6
Breakpoint 2 at /Users/amirabdi/pdb_like_a_pro/fibonacci.py:6
(Pdb++) b
Num Type Disp Enb Where
1 breakpoint del yes at /Users/amirabdi/pdb_like_a_pro/fibonacci.py:5
2 breakpoint keep yes at /Users/amirabdi/pdb_like_a_pro/fibonacci.py:6
Code Execution
The following pdb
commands can be used to control the execution of the code: step
, next
, until
, return
, continue
, and jump
. Here is a quick summary of the most useful ones, step
, next
, return
, and until
.
s[tep] vs n[ext]: `
step`
steps into the function calls, while `next`
steps over them.
step
(step into): The step
command executes the current line and steps into a called function or stops on the next line in the current function.
(Pdb++) l .
6
7 def func2(sky, ocean):
8 x = 10
9 y = x
10 breakpoint()
11 -> func3(sky, ocean, 3)
12 z = y
13 alpha = sum([x, y, z])
14
15
16 def func1(sky):
(Pdb++) s
--Call--
[5] > /Users/amirabdi/pdb_like_a_pro/foo/bar.py(3)func3()
-> def func3(sky, ocean, sea):
(Pdb++) l .
1 # foo/bar.py
2
3 -> def func3(sky, ocean, sea):
4 print('func1', sky, ocean, sea)
5
6
7 def func2(sky, ocean):
8 x = 10
9 y = x
10 breakpoint()
11 func3(sky, ocean, 3)
next
(step over): Thenext
command continues the execution until the next line in the current function is reached, so, it steps over function calls.
(Pdb++) l .
6
7 def func2(sky, ocean):
8 x = 10
9 y = x
10 breakpoint()
11 -> func3(sky, ocean, 3)
12 z = y
13 alpha = sum([x, y, z])
14
15
16 def func1(sky):
(Pdb++) n
func1 1 2 3
[4] > /Users/amirabdi/pdb_like_a_pro/foo/bar.py(12)func2()
-> z = y
(Pdb++) l .
7 def func2(sky, ocean):
8 x = 10
9 y = x
10 breakpoint()
11 func3(sky, ocean, 3)
12 -> z = y
13 alpha = sum([x, y, z])
14
15
16 def func1(sky):
17 func2(sky, 2)
r[eturn]: Continue execution until the current function returns.
✦ ❯ python -m foo.bar
[5] > /Users/amirabdi/pdb_like_a_pro/foo/bar.py(5)func3()
-> x = 10
(Pdb++) l .
3 def func3(sky, ocean, sea):
4 breakpoint()
5 -> x = 10
6 y = x
7 z = y
8 alpha = sum([x, y, z])
9 print('func1', sky, ocean, sea)
10
(Pdb++) return
func1 1 2 3
--Return--
[5] > /Users/amirabdi/pdb_like_a_pro/foo/bar.py(9)func3()->None
-> print('func1', sky, ocean, sea)
(Pdb++) l .
4 breakpoint()
5 x = 10
6 y = x
7 z = y
8 alpha = sum([x, y, z])
9 -> print('func1', sky, ocean, sea)
10
11
12 def func2(sky, ocean):
13 func3(sky, ocean, 3)
14
unt[il] <line_number>: The until
command continues exection until it reaches a line number greater than the specified line_number
argument. If no argument is specified, current line is used by default. This is particularly useful when you want to jump after a loop.
(Pdb++) l .
1 # foo/bar.py
2
3 def func3(sky, ocean, sea):
4 breakpoint()
5 -> x = 10
6 y = x
7 z = y
8 alpha = sum([x, y, z])
9 print('func1', sky, ocean, sea)
10
11
(Pdb++) until 8
[5] > /Users/amirabdi/pdb_like_a_pro/foo/bar.py(8)func3()
-> alpha = sum([x, y, z])
(Pdb++) l .
3 def func3(sky, ocean, sea):
4 breakpoint()
5 x = 10
6 y = x
7 z = y
8 -> alpha = sum([x, y, z])
9 print('func1', sky, ocean, sea)
10
11
12 def func2(sky, ocean):
13 func3(sky, ocean, 3)