Introduction
The debugger is a program that helps us to find what is going on in our program. You can use the debugger to execute each line of code, print variables in each step, break execution at any line, again start execution, stop execution and repeat the same steps until you find the bug in the program.
Importance of Debugging
The debugger is very important for all programming languages. Sometimes while writing a large program, during execution program can misbehave. It may be difficult to trace the exact point where the program is misbehaving. So, the debugger can be used to find such points by executing the program line by line.
The objective of the debugger is to find the error in the program and fix it. Debugger helps you to understand the actual program flow and locate the point where the error is present and the point where the program is not working smoothly due to which there will be a mess while handling the program.
Useful commands with their functions in pdb module:
args(a) ⇒ gives a list of arguments of the current function
continue(c) ⇒ creates a breakpoint in the program
help(h) ⇒ provides a list of commands
jump(j) ⇒ jump to the next line to be executed
list(l) ⇒ gives you the line in which the program currently is
p expression ⇒ print the value of the expression
pp expression ⇒ pretty prints the values of expression
quit(q) ⇒ terminates the program
return(r) ⇒ continue execution until the current function returns
next(n) ⇒ execute next line
step(s) ⇒ execute and step in function
up(u) ⇒ move one level up to stack frame
down(d) ⇒ move one level down to stack frame
breakpoint(b) ⇒ show all breakpoint with the number
<ENTER> key ⇒ execute the last command
unt(il) ⇒ execute until the next line with a number greater than the current reached
The different ways of getting the pdb prompt
Script mode
In this mode, the debugger steps through the entire pdb script.
print("This is debugging in python\n") print("Let's start\n") def div(a,b): result = a/b return result print(div(5, 0))
Output
/storage/emulated/0 $ python -m pdb newfile.py > /storage/emulated/0/newfile.py(1)<module>() -> print("This is debugging in python\n") (Pdb) l 1 -> print("This is debugging in python\n") 2 print("Let's start\n") 3 4 def div(a,b): 5 result = a/b 6 return result 7 8 print(div(5, 0)) [EOF] (Pdb) n This is debugging in python > /storage/emulated/0/newfile.py(2)<module>() -> print("Let's start\n") (Pdb) l 1 print("This is debugging in python\n") 2 -> print("Let's start\n") 3 4 def div(a,b): 5 result = a/b 6 return result 7 8 print(div(5, 0)) [EOF] (Pdb) n Let's start > /storage/emulated/0/newfile.py(4)<module>() -> def div(a,b): (Pdb) l 1 print("This is debugging in python\n") 2 print("Let's start\n") 3 4 -> def div(a,b): 5 result = a/b 6 return result 7 8 print(div(5, 0)) [EOF] (Pdb) n > /storage/emulated/0/newfile.py(8)<module>() -> print(div(5, 0)) (Pdb) l 3 4 def div(a,b): 5 result = a/b 6 return result 7 8 -> print(div(5, 0)) [EOF] (Pdb) n ZeroDivisionError: division by zero > /storage/emulated/0/newfile.py(8)<module>() -> print(div(5, 0)) (Pdb) q /storage/emulated/0 $
Analysis of output of above code, list(l) command is used to printing the source code pointing which line was currently running and next(n) command for execution of code line by line.
To run this code, the python terminal was used and gave the command as python -m pdb filename.py. We can see there was an error when the print(div(5,0)) statement got executed. Quit(q) command was used to get out of the program.
Postmortem mode
This mode is used after an uncaught exception has been raised
def add(a,b): result = a+b return result print(add(5, "b"))
Output
/storage/emulated/0 $ python -i newfile.py Traceback (most recent call last): File "newfile.py", line 5, in <module> print(add(5, "b")) File "newfile.py", line 2, in add result = a+b TypeError: unsupported operand type(s) for +: 'int' and 'str' >>> import pdb >>> pdb.pm() > /storage/emulated/0/newfile.py(2)add() -> result = a+b (Pdb)
This mode tells us where we should start debugging after an exception had occurred.
Run mode
In this mode, some expression is passed to run() function of pdb module to see it generates an error or not
print(6+7) def add(a,b): result = a+b return result print(add(5, 6))
Output
/storage/emulated/0 $ python -i newfile.py Debugger 13 Traceback (most recent call last): File "newfile.py", line 7, in <module> print(aad(5, 6)) NameError: name 'aad' is not defined >>> import pdb >>> pdb.run('print(5+6)') > <string>(1)<module>() (Pdb) s 11 --Return-- > <string>(1)<module>()->None (Pdb) q >>> pdb.run('print(aad(5,6))') > <string>(1)<module>()->None (Pdb) s NameError: name 'aad' is not defined > <string>(1)<module>()->None (Pdb)
As we can see the run() function of the pdb module didn’t throw an error while executing the first statement but threw NameError while executing the statement calling the function because the function name was given incorrect while calling it.
Trace mode
All the modes till now are quite useful but not so efficient when we want to debug a large program. Execution of code is line by line and from the first line of code by default which is not so efficient. This trace mode can start debugging from any line of code using the set_trace() function.
import pdb print("This is debugging in python") class Calculator: def __init__(self, num1, num2): self.a = num1 self.b = num2 def add(self): result = self.a + self.b print("Sum: ", result) def sub(self): result = self.a - self.b print("Subtraction: ", result) def mul(self): print("multiplication: ", self.a*self.b) def div(self): pdb.set_trace() print("division: ", self.a/self.b) cal = Calculator(6,0) cal.div()
Output
This is debugging in python > /home/dcoder/hh/main.py(22)div() -> print("division: ", self.a/self.b) (Pdb) l 17 def mul(self): 18 print("multiplication: ", self.a*self.b) 19 20 def div(self): 21 pdb.set_trace() 22 -> print("division: ", self.a/self.b) 23 24 cal = Calculator(6,0) 25 cal.div() [EOF] (Pdb) n ZeroDivisionError: division by zero > /home/dcoder/hh/main.py(22)div() -> print("division: ", self.a/self.b) (Pdb)
When we divide 6 by 0 there will be an error. So we can use set_trace() function directly in div function of calculator class. If the above method had been used then we had to go line by line to trace the error which could be time-consuming.
Debugging is not only used to trace the error in the program but also used to inspect the program (how the program behaving, how the variables are changing). There could be the case when the program is syntactically correct but not logically. We may not get the desired output if there are no errors in the program. In such conditions, a debugger plays a vital role to correct the logic by inspecting of program.
To explain this, let’s take a problem related to fizzbuzz algorithm. The rules are: if a number passed to a function is divisible by 3, the function should return “fizz” if the number is divisible by 5 the function should return “Buzz” if the number is divisible by both 3 & 5 the function should return “fizzbuzz” and if the number is neither divisible by 5 nor 3 functions should return the same number passed as argument.
def fizz_buzz(num): if num % 3 == 0: return "fizz" elif num % 5 == 0: return "buzz" elif (num % 3 == 0) and (num % 5 == 0): return "fizzbuzz" else: return num print(fizz_buzz(6)) print(fizz_buzz(10)) print(fizz_buzz(15))
Output
fizz buzz fizz
We got the correct output for 6 and 10. But when 15 is passed to function, it is returning “fizz” instead of “fizzbuzz”. Let’s inspect the program where is the logical error using pdb module
import pdb def fizz_buzz(num): pdb.set_trace() if num % 3 == 0: return "fizz" elif num % 5 == 0: return "buzz" elif (num % 3 == 0) and (num % 5 == 0): return "fizzbuzz" else: return num print(fizz_buzz(6)) print(fizz_buzz(10)) print(fizz_buzz(15))
Output
-> if num % 3 == 0: (Pdb) l 1 import pdb 2 def fizz_buzz(num): 3 pdb.set_trace() 4 -> if num % 3 == 0: 5 return "fizz" 6 elif num % 5 == 0: 7 return "buzz" 8 elif (num % 3 == 0) and (num % 5 == 0): 9 return "fizzbuzz" 10 else: 11 return num (Pdb) n > /home/dcoder/hh/main.py(5)fizz_buzz() -> return "fizz"
When 6 is passed to the fizz_buzz function, it returns “fizz” which is correct. So let’s move ahead
(Pdb) n fizz > /home/dcoder/hh/main.py(15)<module>() -> print(fizz_buzz(10)) (Pdb) l 10 else: 11 return num 12 13 print(fizz_buzz(6)) 14 15 -> print(fizz_buzz(10)) 16 17 print(fizz_buzz(15)) [EOF] (Pdb) n > /home/dcoder/hh/main.py(4)fizz_buzz() -> if num % 3 == 0: (Pdb) l 1 import pdb 2 def fizz_buzz(num): 3 pdb.set_trace() 4 -> if num % 3 == 0: 5 return "fizz" 6 elif num % 5 == 0: 7 return "buzz" 8 elif (num % 3 == 0) and (num % 5 == 0): 9 return "fizzbuzz" 10 else: 11 return num (Pdb) n > /home/dcoder/hh/main.py(6)fizz_buzz() -> elif num % 5 == 0: (Pdb) l 1 import pdb 2 def fizz_buzz(num): 3 pdb.set_trace() 4 if num % 3 == 0: 5 return "fizz" 6 -> elif num % 5 == 0: 7 return "buzz" 8 elif (num % 3 == 0) and (num % 5 == 0): 9 return "fizzbuzz" 10 else: 11 return num (Pdb) n > /home/dcoder/hh/main.py(7)fizz_buzz() -> return "buzz" (Pdb)
When next(n) command is passed ‘print(fizz_buzz(10))’ got executed. We can see that the first condition is not matched so it moved to the next condition. Since the condition matched it returned “buzz” which is correct.
Now, when 15 is passed to function, it should match the third condition as 15 is divisible by both 3 and 5 and should return “fizzbuzz”. But as the output is seen earlier, the program didn’t reach the third condition. Let’s see what has actually happened
-> print(fizz_buzz(15)) (Pdb) l 12 13 print(fizz_buzz(6)) 14 15 print(fizz_buzz(10)) 16 17 -> print(fizz_buzz(15)) [EOF] (Pdb) n > /home/dcoder/hh/main.py(4)fizz_buzz() -> if num % 3 == 0: (Pdb) l 1 import pdb 2 def fizz_buzz(num): 3 pdb.set_trace() 4 -> if num % 3 == 0: 5 return "fizz" 6 elif num % 5 == 0: 7 return "buzz" 8 elif (num % 3 == 0) and (num % 5 == 0): 9 return "fizzbuzz" 10 else: 11 return num (Pdb) n > /home/dcoder/hh/main.py(5)fizz_buzz() -> return "fizz" (Pdb) l 1 import pdb 2 def fizz_buzz(num): 3 pdb.set_trace() 4 if num % 3 == 0: 5 -> return "fizz" 6 elif num % 5 == 0: 7 return "buzz" 8 elif (num % 3 == 0) and (num % 5 == 0): 9 return "fizzbuzz" 10 else: 11 return num (Pdb)
When ‘print(fizz_buzz(15))’ is executed, the first condition got matched and return “fizz” which is incorrect. The program didn’t check whether the number is divisible by both 5 & 3. It means the number passed to function should check whether it is divisible by both 3 and 5 or not at first then go to other conditions.
Let’s make a change and see the outcome
def fizz_buzz(num): if (num % 3 == 0) and (num % 5 == 0): return "fizzbuzz" elif num % 5 == 0: return "buzz" elif (num % 3 == 0): return "fizz" else: return num print(fizz_buzz(6)) print(fizz_buzz(10)) print(fizz_buzz(15)) print(fizz_buzz(67))
Output
fizz buzz fizzbuzz 67
Here, we got the correct output now. The whole flow of the program is shown below
import pdb def fizz_buzz(num): pdb.set_trace() if (num % 3 == 0) and (num % 5 == 0): return "fizzbuzz" elif num % 5 == 0: return "buzz" elif (num % 3 == 0): return "fizz" else: return num pdb.set_trace() print(fizz_buzz(6)) print(fizz_buzz(10)) print(fizz_buzz(15)) print(fizz_buzz(67))
Output
-> print(fizz_buzz(6)) (Pdb) n > /home/dcoder/hh/main.py(4)fizz_buzz() -> if (num % 3 == 0) and (num % 5 == 0): (Pdb) l 1 import pdb 2 def fizz_buzz(num): 3 pdb.set_trace() 4 -> if (num % 3 == 0) and (num % 5 == 0): 5 return "fizzbuzz" 6 elif num % 5 == 0: 7 return "buzz" 8 elif (num % 3 == 0): 9 return "fizz" 10 else: 11 return num (Pdb) n > /home/dcoder/hh/main.py(6)fizz_buzz() -> elif num % 5 == 0: (Pdb) l 1 import pdb 2 def fizz_buzz(num): 3 pdb.set_trace() 4 if (num % 3 == 0) and (num % 5 == 0): 5 return "fizzbuzz" 6 -> elif num % 5 == 0: 7 return "buzz" 8 elif (num % 3 == 0): 9 return "fizz" 10 else: 11 return num (Pdb) n > /home/dcoder/hh/main.py(8)fizz_buzz() -> elif (num % 3 == 0): (Pdb) l 3 pdb.set_trace() 4 if (num % 3 == 0) and (num % 5 == 0): 5 return "fizzbuzz" 6 elif num % 5 == 0: 7 return "buzz" 8 -> elif (num % 3 == 0): 9 return "fizz" 10 else: 11 return num 12 13 pdb.set_trace() (Pdb) n > /home/dcoder/hh/main.py(9)fizz_buzz() -> return "fizz" (Pdb) n --Return-- > /home/dcoder/hh/main.py(9)fizz_buzz()->'fizz' -> return "fizz" (Pdb) n fizz > /home/dcoder/hh/main.py(16)<module>() -> print(fizz_buzz(10)) (Pdb) l 11 return num 12 13 pdb.set_trace() 14 print(fizz_buzz(6)) 15 16 -> print(fizz_buzz(10)) 17 18 print(fizz_buzz(15)) 19 20 print(fizz_buzz(67)) [EOF] (Pdb) n > /home/dcoder/hh/main.py(4)fizz_buzz() -> if (num % 3 == 0) and (num % 5 == 0): (Pdb) l 1 import pdb 2 def fizz_buzz(num): 3 pdb.set_trace() 4 -> if (num % 3 == 0) and (num % 5 == 0): 5 return "fizzbuzz" 6 elif num % 5 == 0: 7 return "buzz" 8 elif (num % 3 == 0): 9 return "fizz" 10 else: 11 return num (Pdb) n > /home/dcoder/hh/main.py(6)fizz_buzz() -> elif num % 5 == 0: (Pdb) l 1 import pdb 2 def fizz_buzz(num): 3 pdb.set_trace() 4 if (num % 3 == 0) and (num % 5 == 0): 5 return "fizzbuzz" 6 -> elif num % 5 == 0: 7 return "buzz" 8 elif (num % 3 == 0): 9 return "fizz" 10 else: 11 return num (Pdb) n > /home/dcoder/hh/main.py(7)fizz_buzz() -> return "buzz" (Pdb) n --Return-- > /home/dcoder/hh/main.py(7)fizz_buzz()->'buzz' -> return "buzz" (Pdb) n buzz > /home/dcoder/hh/main.py(18)<module>() -> print(fizz_buzz(15)) (Pdb) l 13 pdb.set_trace() 14 print(fizz_buzz(6)) 15 16 print(fizz_buzz(10)) 17 18 -> print(fizz_buzz(15)) 19 20 print(fizz_buzz(67)) [EOF] (Pdb) n > /home/dcoder/hh/main.py(4)fizz_buzz() -> if (num % 3 == 0) and (num % 5 == 0): (Pdb) l 1 import pdb 2 def fizz_buzz(num): 3 pdb.set_trace() 4 -> if (num % 3 == 0) and (num % 5 == 0): 5 return "fizzbuzz" 6 elif num % 5 == 0: 7 return "buzz" 8 elif (num % 3 == 0): 9 return "fizz" 10 else: 11 return num (Pdb) n > /home/dcoder/hh/main.py(5)fizz_buzz() -> return "fizzbuzz" (Pdb) l 1 import pdb 2 def fizz_buzz(num): 3 pdb.set_trace() 4 if (num % 3 == 0) and (num % 5 == 0): 5 -> return "fizzbuzz" 6 elif num % 5 == 0: 7 return "buzz" 8 elif (num % 3 == 0): 9 return "fizz" 10 else: 11 return num (Pdb) n --Return-- > /home/dcoder/hh/main.py(5)fizz_buzz()->'fizzbuzz' -> return "fizzbuzz" (Pdb) l 1 import pdb 2 def fizz_buzz(num): 3 pdb.set_trace() 4 if (num % 3 == 0) and (num % 5 == 0): 5 -> return "fizzbuzz" 6 elif num % 5 == 0: 7 return "buzz" 8 elif (num % 3 == 0): 9 return "fizz" 10 else: 11 return num (Pdb) n fizzbuzz > /home/dcoder/hh/main.py(20)<module>() -> print(fizz_buzz(67)) (Pdb) n > /home/dcoder/hh/main.py(4)fizz_buzz() -> if (num % 3 == 0) and (num % 5 == 0): (Pdb) l 1 import pdb 2 def fizz_buzz(num): 3 pdb.set_trace() 4 -> if (num % 3 == 0) and (num % 5 == 0): 5 return "fizzbuzz" 6 elif num % 5 == 0: 7 return "buzz" 8 elif (num % 3 == 0): 9 return "fizz" 10 else: 11 return num (Pdb) n > /home/dcoder/hh/main.py(6)fizz_buzz() -> elif num % 5 == 0: (Pdb) l 1 import pdb 2 def fizz_buzz(num): 3 pdb.set_trace() 4 if (num % 3 == 0) and (num % 5 == 0): 5 return "fizzbuzz" 6 -> elif num % 5 == 0: 7 return "buzz" 8 elif (num % 3 == 0): 9 return "fizz" 10 else: 11 return num (Pdb) n > /home/dcoder/hh/main.py(8)fizz_buzz() -> elif (num % 3 == 0): (Pdb) l 3 pdb.set_trace() 4 if (num % 3 == 0) and (num % 5 == 0): 5 return "fizzbuzz" 6 elif num % 5 == 0: 7 return "buzz" 8 -> elif (num % 3 == 0): 9 return "fizz" 10 else: 11 return num 12 13 pdb.set_trace() (Pdb) n > /home/dcoder/hh/main.py(11)fizz_buzz() -> return num (Pdb) l 6 elif num % 5 == 0: 7 return "buzz" 8 elif (num % 3 == 0): 9 return "fizz" 10 else: 11 -> return num 12 13 pdb.set_trace() 14 print(fizz_buzz(6)) 15 16 print(fizz_buzz(10)) (Pdb) n --Return-- > /home/dcoder/hh/main.py(11)fizz_buzz()->67 -> return num (Pdb) n 67 --Return--
Conclusion
This is how a debugger can be used in detecting errors and also examining the flow of the program. Sometimes there can be some error in logic implementation but not a syntactic error, debugger guides the programmer to look through the code, see how the program is reacting, how variables are changing, how flow control is changing, and many more.
Programmers can set a breakpoint at any line of code and inspect the flow to observe what is actually going on. So, debugging tool is very important for detecting errors and inspecting the flow of the program.
Reference
https://realpython.com/python-debugging-pdb/
Happy learning 🙂