5 Best Ways to Use the Python Debugger PDB

πŸ’‘ Problem Formulation: Debugging is an integral part of software development, allowing developers to dissect and understand their code to fix bugs. In this article, we’ll explore how the Python Debugger (pdb) can be employed to troubleshoot a Python script where a function is not returning the expected results. We’ll look into different pdb commands and techniques to pinpoint the issue, providing a guide to transform an erroneous input into desired output efficiently.

Method 1: Starting pdb at Runtime

Activating pdb during runtime allows developers to halt execution at a specific point and examine the state of the program. This method provides full control over the execution, enabling step-by-step code analysis. The pdb.set_trace() function is used to initiate the debugger at the desired point in the script.

β™₯️ Info: Are you AI curious but you still have to create real impactful projects? Join our official AI builder club on Skool (only $5): SHIP! - One Project Per Month

Here’s an example:

import pdb

def compute_sum(a, b):
    pdb.set_trace()
    return a + b

result = compute_sum(2, 2)
print(result)

Output:

> <stdin>(1)compute_sum()
(Pdb) 

This code snippet introduces a breakpoint using pdb.set_trace() inside the compute_sum() function. When the script is run, pdb activates and gives control to the developer before executing the return statement, enabling inspection of variables, stepping through the code, and manipulating the execution flow.

Method 2: Post-Mortem Debugging

Post-mortem debugging is used after an exception has caused a script to crash. pdb’s post-mortem feature can be invoked to inspect the state of the program at the time of the exception. The command pdb.pm() can be called in the script’s except block to start the debugger.

Here’s an example:

import pdb

def faulty_division(a, b):
    return a / b

try:
    result = faulty_division(10, 0)
except ZeroDivisionError:
    print("Cannot divide by zero!")
    pdb.pm()

Output:

Cannot divide by zero!
> <stdin>(1)faulty_division()
(Pdb) 

In the provided example, attempting to divide by zero raises a ZeroDivisionError. Within the except block, calling pdb.pm() starts pdb in post-mortem mode, providing a debugging session at the moment the exception occurred, which is useful for diagnosing the cause of the crash.

Method 3: Running pdb as a Script

Running a Python script with pdb allows for thorough inspection from the very start of execution. This is done by calling python with the -m pdb option followed by the script’s filename. This mode is beneficial for analyzing problems that occur during script initialization and setup.

Here’s an example:

# in your terminal run:
# python -m pdb my_script.py

Output:

> /path/to/my_script.py(1)<module>()
(Pdb) 

This command runs the script named my_script.py under pdb’s control from the start. When the debugger starts, it halts before the first line of the script, allowing developers to execute, step through, set breakpoints, and examine variables right from the beginning.

Method 4: Inspecting Variable States

Inspecting variable states is vital while debugging. pdb provides commands like p to print the current value of a variable and pp to display more complex variables in a formatted manner. This helps in monitoring how data is manipulated as the code runs.

Here’s an example:

import pdb

def multiply(a, b):
    pdb.set_trace()
    return a * b

result = multiply(5, 3)
print(result)

Output:

> <stdin>(1)multiply()
(Pdb) p a
5
(Pdb) p b
3
(Pdb) 

Here, pdb.set_trace() activates the debugger within the multiply() function. The pdb commands p followed by a variable name prints its current value to the console. This assists in verifying the contents of variables as needed during a debugging session.

Bonus One-Liner Method 5: One-liner Breakpoint

The one-liner breakpoint strategy involves integrating pdb activation directly into a Python statement, compact and suitable for quick debugging tasks. It relies on the succinct breakpoint() built-in function, available from Python 3.7 and onward. This function automatically triggers pdb where it is called.

Here’s an example:

def calculate_difference(a, b):
    breakpoint()
    return a - b

result = calculate_difference(10, 4)
print(result)

Output:

> <stdin>(2)calculate_difference()
(Pdb) 

By inserting a breakpoint() inside the calculate_difference() function, it pauses execution upon reaching this point, thus invoking pdb. This convenience function provides a quick way to enter the debugger without explicitly importing or calling pdb-related functions.

Summary/Discussion

  • Method 1: Starting pdb at Runtime. Strengths: Can be placed precisely where needed in the code. Weaknesses: Requires code modification and might clutter the codebase if not removed after debugging.
  • Method 2: Post-Mortem Debugging. Strengths: Useful for examining the state at the time of an exception. Weaknesses: Only applicable after an unhandled exception has occurred.
  • Method 3: Running pdb as a Script. Strengths: Good for understanding initialization issues. Weaknesses: Can be cumbersome for long-running scripts where the problem occurs much later.
  • Method 4: Inspecting Variable States. Strengths: Allows for detailed inspection of data at any point. Weaknesses: Interrupts flow of program, and requires manual interaction to print variables.
  • Method 5: One-liner Breakpoint. Strengths: Quick and easy to use without importing pdb separately. Weaknesses: As a new feature, not available in older Python versions.