Debugging in Python | Python Debugger

After a writing the program in Python or any other computer programming language, the next step is to debug the program. When we write a computer program in a language, we call it as program code or simply coding.

When writing these codes, we sometimes make mistakes. These mistakes are called programming errors or bugs in computer language, and the process of locating and fixing them is called debugging in Python. So, we can define debugging as:

The process of identify bugs or errors and correcting them in a computer program is called debugging. It is essential to ensure that the program code runs correctly and efficiently.

During the debugging phase, our goal is to find errors or bugs. Debuggers are powerful tools that allow us to simultaneously run a program and see which line of source code is currently being executed. We can watch the value of variables and other program elements to see if their value change as expected.

Debugging Techniques in Python


Debugging techniques are an essential part of the programming that allows us to identify and fix errors in the computer programs. A good understanding of different techniques can help us to locate and correct errors more quickly and efficiently in the program code.

Debugging techniques in Python

1. print statement: This is a common debugging technique which allows us to see values of variables at different points in the program code. It can help to locate where and bug is occurring and what is causing it.

2. Debugger: This is another technique which is a tool allowing us to debug the code line by line and observe the values of variables at each step. It can help us to understand how is the code executing and identify the source of a bug.

3. assert statement: This technique allows us to validate that certain conditions are true at specific points in the program code. If an assert statement fails, the program will stop further executing, allowing to identify the issues or errors in the code.


4. unit test: This technique is a test which checks a small piece of code to ensure that it is properly functioning or not. The unit testing helps us to identify errors early on and ensures that changes to the code do not make to cause unintended consequences.

5. version control system: This technique allows us to track changes to our program code over time. It can help to identify when a bug was introduced and who was responsible for it.

At last, it is also important to take a break, clear your mind, and come back later to the problem. Sometimes, a fresh perspective and a clear mind can be incredibly helpful to identify the problem with solution more easily.

Python Debugger


Debugging is an essential part of the Python or any other programming languages. Debuggers are valuable tools for tracking bugs and fixing programs that contain bugs. They can speed up the debugging process a lot faster than using simple print() function throughout the code, which can be much more efficient than manually inserting print statements to identify issues.

With a debugger, we can systematically step through our program one line, or one code block at a time. Using a debugger typically involves the following steps:

(1) Setting Breakpoints: Set specific points in your code where you want the debugger to pause execution so that you can inspect the state of the program.

(2) Running the Code in Debug Mode: Execute your program using the debugger, allowing it to run until it reaches the breakpoints you’ve set.

(3) Inspecting Variables and State: When the code pauses at a breakpoint, you can examine the values of variables, check the program’s state, and understand how the code is executing at that point.

(4) Stepping Through Code: Navigate through the code step by step, line by line, or through function calls to track the flow of execution and identify any errors or unexpected behavior.

(5) Making Adjustments and Fixing Issues: With insights gained from the debugger, you can make necessary adjustments to your code to fix bugs or improve its performance.

These steps help in understanding the program’s behavior, pinpointing issues, and ultimately resolving them efficiently.

pdb Module for Debugging in Python


The module pdb is an interactive source code debugger that’s built for Python programs. It is basically a Python standard library. It supports conditional breakpoints setting and single stepping at the source line level, inspection of stack frames, source code listing, and post-mortem debugging that can be called under the program control. The pdb module provides the functions that are as follows:

Python Debugger Functions

1. run(statement[, globals[, locals]]): This function executes the statement provided as a string. Before the code is executed, the debugger prompt appears, allowing you to set breakpoints and choose how you want to proceed, whether by using “continue” to run until the next breakpoint, or by stepping through the statement using “step” or “next” to navigate line by line.

In the above run() function, the statement represents the code or statement to be executed (usually provided as a string). The globals and locals are optional parameters representing the global and local variable dictionaries to be used during execution. These parameters may or may not be present depending on the specific debugger implementation.

2. runeval(expression[, globals[, locals]]): This function tests the expression provided as a string and returns the value of the expression.

3. runcall(function[, argument, . . . ]): The runcall() function in the debugging modules like pdb calls a specified function along with its arguments. Here, the parameter function refers to the function that you want to execute. The arguments that we pass to the function being executed. We can include multiple arguments, depending on the function’s requirements.

4. set_trace(): The set_trace() function is a command typically used to initiate or set a breakpoint in the code at a specific location. When the Python interpreter encounters this command while executing the code, it pauses the execution and enters into an interactive debugging mode.

Once the code execution is paused at the breakpoint set by set_trace(), we gain access to an interactive console or debugger prompt. From there, we can inspect variables, check the program’s state, execute code line by line, and navigate through the execution flow of our program, helping us analyze and debug the code effectively.

The parameter traceback accepts a traceback object, which contains information about the sequence of function calls that led to the error or exception.

5. post_mortem(traceback): The post_mortem() function is used to start a post-mortem debugging session based on the provided traceback object.

6. pm(): This function is used to start post-mortem debugging of the traceback found in sys.last_traceback.

Python Debugger Commands


The functions provided by the pdb module debugger works with debugger commands. Some of the useful debugger commands in Python are as follows:

1. help: This command helps to view a list of all commands or view help for a specific command. It is often abbreviated as h. Without argument, it will print the list of available of commands. To see a list of all available commands, type the below syntax.

(Pdb) help
Output:
        Documented commands (type help ):
        ========================================
        EOF    c          d        h         list      q        rv       undisplay
        a      cl         debug    help      ll        quit     s        unt      
        alias  clear      disable  ignore    longlist  r        source   until    
        args   commands   display  interact  n         restart  step     up       
        b      condition  down     j         next      return   tbreak   w        
        break  cont       enable   jump      p         retval   u        whatis   
        bt     continue   exit     l         pp        run      unalias  where    

        Miscellaneous help topics:
        ==========================
        exec  pdb

With a command as an argument, it will print help about that command. The help() function is used to access information about available commands or specific commands within the Python interpreter.

When invoked without any arguments, it displays a list of available commands. If you provide a specific command as an argument, it will show detailed information and usage instructions for that particular command.  For example:

(Pdb) help a
Output:
        a(rgs)
        Print the argument list of the current function.

2. down: This command in the Python debugger (pdb) is used to move the current frame one level down to a newer frame in the stack trace. It is often abbreviated as d.

3. up: This command in the Python debugger (pdb) is used to move the current frame one level up to an older frame in the stack trace. It is often abbreviated as u.

4. break: This command is used to set breakpoints at the line numbers in the code. A breakpoint is a specific point in your code where you want the debugger to pause and give you control for manual inspection. When the execution of your program reaches a breakpoint, it stops, and the pdb prompt is activated, allowing you to interact with the code. It is often abbreviated as b.

5. clear: The clear command is used to remove breakpoints that have been set in your code. It is generally abbreviated as c.

6. list: This command displays the source code around the current line where the debugger is paused. It is often abbreviated as l. The list command is useful for getting a quick overview of the source code near the point where the debugger is paused, helping us understand the surrounding context during the debugging process.

7. where: This command displays the file and line number of the current line. It is often abbreviated as w.

8. next: This command executes the current line of code and then stops at the next line in the code. It allows you to step through your code one line at a time during a debugging session. It is often abbreviated as n.

9. step: The step command in the Python debugger pdb steps into the functions called or subroutine at the current line. It is often abbreviated as s.

10. return: This command executes the program until the current function’s return is encountered. It is often abbreviated as r. The return command is useful when you are inside a function and want to resume execution until the function completes and returns control to the point where the function was called. It also allows you to exit the current function and continue executing the code that follows the function call.

11. continue: This command continues the execution of the program, only stops when a breakpoint is encountered. The continue command is helpful when you want to skip past a specific part of your code during debugging and allow the program to run until it finishes or encounters another breakpoint. This command is often abbreviated as c.

12. p<name>: This command prints the value of variables, expressions, or other Python constructs during a debugging session. To display the value of a variable at the pdb prompt, type “p” (for print) followed by the name of the variable you want to print and press the Enter key. The general syntax is as:

(Pdb) p variable_name

13. q(uit): This command is used to exit the debugger.

Traces / Breakpoints


If you want to enter the debugger at a specific point in your code without relying on an error, you can set a trace or a breakpoint at that particular location. This allows you to pause the execution of the program at that point and examine the state of variables, check the flow of the program, and analyze the code’s behavior without needing an error to trigger the debugger.

You can only use the debugger function set_trace() to set a trace or breakpoint after importing the pdb module. The general syntax to import pdb module in the program is as:

import pdb

After importing the pdb module in the program, you can use pdb.set_trace() anywhere in the program code. When the interpreter will execute this statement, the execution of the program will stop and you will get a debugger prompt (pdb).

Let’s take an example program in which we will set a trace or breakpoint using set_trace() function.

Example 1:

# Importing the Python pdb module.
import pdb
a = "ICSE"
# Set a break point here.
pdb.set_trace()
b = "Class - 10"
c = "Python course"
String = a + b + c
print(String)

Now run this script code, it will execute until it encounters the pdb.set_trace() line. At this point, the execution will pause, and the Python Debugger (pdb) prompt will be activated as shown in the below figure. It allows you to interact with the code and waits for the input from the programmer in the current statement displayed in the prompt.

Python debugging pdb prompt

Here, you can use the debugger commands shown in the above section at the pdb prompt. At the debugger pdb prompt, type the lowercase letter “n” command (which stands for “next”) from your keyboard, and then press the Enter key. This will tell the pdb to execute the current statement and move to the next one in the program code.

This allows you to step through the code one statement at a time. You can repeat this process by typing n and pressing Enter again to execute the next statement. Continue doing this until you will come to the end of your program, and it will terminate and return you to the normal command prompt.

Once you’re done with debugging, you can type q and press Enter to quit the pdb session and return to the normal command prompt.

Example Programs based on Debugger Commands


Let’s take some important example programs based on the above debugger commands that you must practice for debugging in Python.

Example 2: Printing the value of variable(s)

# Importing Python debugger pdb module.
import pdb
def add_numbers(a, b):
    result = a + b
    pdb.set_trace()
    return result

x = 5
y = 10
sum_result = add_numbers(x, y)
Output:
      (Pdb) p x
       5
      (Pdb) p y
       10
      (Pdb) p result
       15

In the example, when the debugger pauses at pdb.set_trace(), we have used to “p” command to check the values of x, y, and result.

Example 3: continue command

import pdb
def add_numbers(a, b):
    sum = a + b
    pdb.set_trace()  # First breakpoint
    return sum

def multiply_numbers(a, b):
    multiply = a * b
    pdb.set_trace()  # Second breakpoint
    return multiply

def main():
    x = 3
    y = 7
    sum_result = add_numbers(x, y)
    product_result = multiply_numbers(sum_result, y)
    print("The final result is: ", product_result)

if __name__ == "__main__":
    main()
Output:
       -> return sum
       (Pdb) continue
  
       -> return multiply
       (Pdb) 

In this example, there are two breakpoints set within the add_numbers and multiply_numbers functions. The program first calls add_numbers and pauses at the first breakpoint. After continuing, it then calls multiply_numbers and pauses at the second breakpoint.

The continue command allows the program to run until it either completes or encounters another breakpoint. In this example, it helps us navigate through multiple breakpoints during the debugging process.

Example 4: return command

import pdb
def foo():
    x = 10
    y = 20
    z = x + y
    pdb.set_trace()
    return z

result = foo()
Output:
       (Pdb) return
       --Return--
       > c:\python project\debug1.py(7)foo()->30
       -> return z
       (Pdb) return
       --Return--
       > c:\python project\debug1.py(9)()->None
       -> result = foo()

In this example, when the debugger pauses at pdb.set_trace(), the return command resumes the execution until the foo function completes, and the result of the function will be stored in the variable result.

Example 5: list command

import pdb
def calculate_sum(a, b):
    result = a + b
    c = a * b
    pdb.set_trace()  # Breakpoint

    # Some more calculations
    d = a - b
    e = a / b

    return result, c, d, e

if __name__ == "__main__":
    x = 10
    y = 5
    result_tuple = calculate_sum(x, y)
    print("Result Tuple:", result_tuple)
Output:
        (Pdb) list 8, 11
        8  -> d = a - b
        9     e = a / b
        10  	
        11    return result, c, d, e

In this example, there’s a calculate_sum function that performs various calculations. We have set a breakpoint using pdb.set_trace(). When the debugger pauses at the breakpoint, the list command will show a few lines of code centered around the line where the debugger is currently paused. Optionally, we can specify a range to display a specific set of lines as (Pdb) list 8, 11.

Example 6: break command

import pdb
def calculation(a, b):
    result = a * b
    pdb.set_trace()  # Breakpoint

    # Another calculation
    squared_result = result ** 2
    return squared_result

if __name__ == "__main__":
    x = 3
    y = 4
    final_result = calculation(x, y)
    print("Final Result:", final_result)
Output:
       (Pdb) break 7
       Breakpoint 1 at c:\python project\debug1.py:7
       (Pdb) break 8
       Breakpoint 2 at c:\python project\debug1.py:8
       (Pdb) break 11
       Breakpoint 3 at c:\python project\debug1.py:11
       (Pdb) break 12
       Breakpoint 4 at c:\python project\debug1.py:12

In this example, there’s a calculation() function that performs a basic multiplication and then squares the result. We have set a breakpoint using pdb.set_trace() after the initial multiplication. We have used a break command to set new breakpoints at specific lines.


In this tutorial, we have explained debugging techniques in Python with the help of various examples. Hope that you will have understood how to debug Python code in Pycharm.
Thanks for reading!!!