From the Oxford dictionary:
The process of identifying and removing errors from computer hardware or software.
Anecdotal definition:
What I resort to when just looking at the code and the problem doesn't result in a root cause.
def sum_of_list(num_list):
"""
Toy implementation of summing a list of numbers. Unfortunately, it's
crashing when we run it.
"""
sum = 0
# In the real world, use the sum() function instead.
for i in num_list:
sum += step_in(i)
print(f"And the sum is... {sum}")
def step_in(num):
"""
I don't do anything, I'm just here to demonstrate stepping in.
"""
return num
if __name__ == "__main__":
sum_of_list([2, 3, 5, "7", 9, 11, "13", 17])
aaron@compy-386:~/pdb-tutorial $ python buggy_function.py
Traceback (most recent call last):
File "buggy_function.py", line 25, in <module>
sum_of_list([2, 3, 5, "7", 9, 11, "13", 17])
File "buggy_function.py", line 12, in sum_of_list
sum += step_in(i)
TypeError: unsupported operand type(s) for +=: 'int' and 'str'
aaron@compy-386:~/pdb-tutorial $
It might be obvious what the problem is, but let's use it to learn pdb!
There are two ways to invoke pdb:
From your terminal
python -m pdb <your_script>.py
for i in num_list:
breakpoint() # pdb will pause here
sum += step_in(i)
Personally, I use the 2nd method 99% of the time.
import pdb; pdb.set_trace() still works if you prefer typing more.
Remember to delete breakpoints before deploying to production 😉
aaron@compy-386$ python buggy_function.py
> ~/pdb-tutorial/buggy_function.py(13)sum_of_list()
-> sum += step_in(i)
(Pdb)
File being debugged | buggy_function.py |
Current line we're paused on | 13 |
Our variable scope/context [>] | sum_of_list() |
Code on paused line [->] | sum += step_in(i) |
We are provided with lots of information:
Command | Summary |
---|---|
q | Quits pdb and aborts execution |
p <expression> | Prints the given expression |
pp <expression> | Pretty-prints the expression |
l |
Show 11 lines of source code around the current line |
n | Execute the current line (step over) |
s | Execute and step into a function |
r | Continue execution until current function returns (step out) |
c | Continue execution until next breakpoint |
Exit pdb and abort program execution.
When working on web projects, this terminates an HTTP connection.
aaron@compy-386$ python buggy_function.py
> ~/pdb-tutorial/buggy_function.py(13)sum_of_list()
-> sum += step_in(i)
(Pdb) q
Traceback (most recent call last):
File "buggy_function.py", line 26, in <module>
sum_of_list([2, 3, 5, "7", 9, 11, "13", 17])
File "buggy_function.py", line 13, in sum_of_list
sum += step_in(i)
File "buggy_function.py", line 13, in sum_of_list
sum += step_in(i)
File "/Users/aaron/.pyenv/versions/3.8.1/lib/python3.8/bdb.py", line 88, in trace_dispatch
return self.dispatch_line(frame)
File "/Users/aaron/.pyenv/versions/3.8.1/lib/python3.8/bdb.py", line 113, in dispatch_line
if self.quitting: raise BdbQuit
bdb.BdbQuit
aaron@compy-386$
Prints the expression.
An expression can be a single variable.
It can also be a comma-separated list of variables.
pdb returns a tuple!
aaron@compy-386$ python buggy_function.py
> ~/pdb-tutorial/buggy_function.py(13)sum_of_list()
-> sum += step_in(i)
(Pdb) p sum
0
(Pdb) p sum, i
(0, 2)
(Pdb) locals()
{'num_list': [2, 3, 5, '7', 9, 11, '13', 17], 'sum': 0, 'i': 2}
(Pdb)
Pro-tip:
The pdb prompt is also a Python interpreter!
Pretty-prints the expression.
Can be useful when variables hold complex data structures.
(Pdb) p locals()
{'num_list': [2, 3, 5, '7', 9, 11, '13', 17], 'sum': 0, 'i': 2}
(Pdb) pp locals()
{'i': 2, 'num_list': [2, 3, 5, '7', 9, 11, '13', 17], 'sum': 0}
Pro-tip:
Use p and pp to safely interpret your expressions
l(ist) [first, [last]]
Shows 11 lines of source code around the current stoppage point.
Specifying a number for [first] shows 11 lines around that line.
Adding [last] returns code within that range.
> ~/pdb-tutorial/buggy_function.py(13)sum_of_list()
-> sum += step_in(i)
(Pdb) l
8 sum = 0
9
10 # In the real world, use the sum() function instead.
11 for i in num_list:
12 breakpoint()
13 -> sum += step_in(i)
14
15 print(f"And the sum is... {sum}")
16
17
18 def step_in(num):
(Pdb)
Continues execution until the next line in your current function.
When looping, "next line" is the start of a new iteration.
Pro-tip: this can get tiring if looping over several items. I won't cover it, but unt(il) can help you get out of these situations.
This is known as "stepping over" your code because the debugger does not pause inside step_in(i), it just runs it.
> ~/pdb-tutorial/buggy_function.py(13)sum_of_list()
-> sum += step_in(i)
(Pdb) p sum, i
(0, 2)
(Pdb) n
> ~/pdb-tutorial/buggy_function.py(11)sum_of_list()
-> for i in num_list:
(Pdb)
Executes the current line then pauses at a function call.
If used on a line without a function call, it's the same as n(ext).
> ~/pdb-tutorial/buggy_function.py(13)sum_of_list()
-> sum += step_in(i)
(Pdb) s
--Call--
> ~/pdb-tutorial/buggy_function.py(18)step_in()
-> def step_in(num):
(Pdb) n
> ~/pdb-tutorial/buggy_function.py(22)step_in()
-> return num
(Pdb)
Executes until the current function returns (or crashes).
Note the return value is printed!
aaron@compy-386$ python buggy_function.py
> ~/pdb-tutorial/buggy_function.py(13)sum_of_list()
-> sum += step_in(i)
(Pdb) s
--Call--
> ~/pdb-tutorial/buggy_function.py(18)step_in()
-> def step_in(num):
(Pdb) r
--Return--
> ~/pdb-tutorial/buggy_function.py(22)step_in()->2
-> return num
(Pdb)
aaron@compy-386$ python buggy_function.py
> ~/pdb-tutorial/buggy_function.py(13)sum_of_list()
-> sum += step_in(i)
(Pdb) r
> ~/pdb-tutorial/buggy_function.py(13)sum_of_list()
-> sum += step_in(i)
(Pdb)
We tried to "step out" of sum_of_list() but pdb found our breakpoint inside the loop.
Continues program execution, pausing only for breakpoints.
aaron@compy-386$ python buggy_function.py
> ~/pdb-tutorial/buggy_function.py(12)sum_of_list()
-> for i in num_list:
(Pdb) l
7 """
8 sum = 0
9 breakpoint()
10
11 # In the real world, use the sum() function instead.
12 -> for i in num_list:
13 sum += step_in(i)
14
15 print(f"And the sum is... {sum}")
16
17
(Pdb) c
Traceback (most recent call last):
File "buggy_function.py", line 26, in <module>
sum_of_list([2, 3, 5, "7", 9, 11, "13", 17])
File "buggy_function.py", line 12, in sum_of_list
for i in num_list:
TypeError: unsupported operand type(s) for +=: 'int' and 'str'
aaron@compy-386$
Note that I moved the breakpoint out of the loop.
In our case, the script crashes.
There are many more commands that will make your debugging life easier!