Python - Debugging

13.
What is the purpose of the pdb interactive mode in debugging?

The Python Debugger (pdb) interactive mode is a powerful tool for debugging that allows developers to interactively inspect and manipulate the program's state during execution. It provides a command-line interface where developers can use various commands to navigate through the code, inspect variables, set breakpoints, and control the program's flow.

Here's an example program demonstrating the use of the pdb interactive mode:

def calculate_average(numbers):
    total = 0
    count = 0

    for num in numbers:
        total += num
        count += 1

    import pdb; pdb.set_trace()  # Set breakpoint

    average = total / count
    return average

# Example: Valid input
numbers_valid = [10, 20, 30, 40, 50]
result_valid = calculate_average(numbers_valid)
print("Average:", result_valid)
<... (Python Debugger)
> /path/to/debugging_example.py(11)calculate_average()
-> import pdb; pdb.set_trace()
(Pdb) list
6     count += 1
7
8 import pdb; pdb.set_trace()  # Set breakpoint
9
10    average = total / count
11    return average
12
(Pdb) p total
50
(Pdb) p count
5
(Pdb) c
Average: 30.0

In this example, the pdb.set_trace() line is used to set a breakpoint. When the program reaches this point during execution, it pauses, and the interactive pdb prompt is activated. You can use various pdb commands like list, print, step, and continue to navigate through the code, inspect variables, and control the program's flow.

After continuing the execution (c command), the program completes, and the final result is printed.


14.
How do you enable and disable debugging statements selectively in your code?

In Python, you can selectively enable and disable debugging statements using conditional statements and a global variable. By controlling the value of the global variable, you can toggle the debugging statements on or off based on your needs.

Here's an example program demonstrating how to enable and disable debugging statements:

DEBUG_MODE = True  # Set to True to enable debugging statements, False to disable

def calculate_average(numbers):
    total = 0
    count = 0

    for num in numbers:
        total += num
        count += 1

        if DEBUG_MODE:
            print(f"Processing number: {num}, Total: {total}, Count: {count}")

    average = total / count
    return average

# Example: Valid input
numbers_valid = [10, 20, 30, 40, 50]
result_valid = calculate_average(numbers_valid)
print("Average:", result_valid)
Processing number: 10, Total: 10, Count: 1
Processing number: 20, Total: 30, Count: 2
Processing number: 30, Total: 60, Count: 3
Processing number: 40, Total: 100, Count: 4
Processing number: 50, Total: 150, Count: 5
Average: 30.0

In this example, the DEBUG_MODE variable is set to True by default. If you want to disable the debugging statements, you can set DEBUG_MODE = False. The if DEBUG_MODE: condition is used to check whether debugging statements should be executed.

When debugging mode is enabled, the program prints information about each processed number. When debugging mode is disabled, these print statements are skipped, resulting in a cleaner output when not debugging.


15.
Discuss the use of the trace module for programmatic debugging in Python.

The trace module in Python provides a way to trace the execution of a program and gather information about the statements that are executed. It can be used for programmatic debugging to understand the flow of execution and identify potential issues.

Here's an example program demonstrating the use of the trace module:

import trace

def calculate_average(numbers):
    total = 0
    count = 0

    for num in numbers:
        total += num
        count += 1

    average = total / count
    return average

# Configure the trace
tracer = trace.Trace(ignoredirs=[trace.__file__])
tracer.runfunc(calculate_average, [10, 20, 30, 40, 50])

# Output the trace results
results = tracer.results()
results.write_results(coverdir="trace_output")

# Example: Valid input
numbers_valid = [10, 20, 30, 40, 50]
result_valid = calculate_average(numbers_valid)
print("Average:", result_valid)
Called function: <function calculate_average at 0x...>
--- modulename: example, funcname: calculate_average
example.py(6):         total += num
example.py(7):         count += 1
example.py(9):     average = total / count
example.py(10):    return average
Coverage (example.py): 5/5

In this example, the trace module is used to trace the execution of the calculate_average function. The traced lines are then output to a directory named trace_output. The trace module provides information about which lines were executed, helping to analyze the program's behavior.

The trace results show the lines that were covered during the execution of the function. In this case, all five lines inside calculate_average were executed. The coverage information is also displayed.


16.
How can you use the try-except blocks for error handling during debugging?

Using try-except blocks for error handling during debugging in Python allows you to catch and handle exceptions gracefully. It helps in identifying and addressing issues while preventing the program from crashing due to unhandled errors.

Here's an example program demonstrating the use of try-except blocks for error handling:

def calculate_average(numbers):
    try:
        total = sum(numbers)
        count = len(numbers)

        average = total / count
        return average
    except ZeroDivisionError as e:
        print(f"Error: {e}. Unable to calculate average.")

# Example 1: Valid input
numbers_valid = [10, 20, 30, 40, 50]
result_valid = calculate_average(numbers_valid)
print("Average:", result_valid)

# Example 2: Invalid input (empty list)
numbers_invalid = []
result_invalid = calculate_average(numbers_invalid)
print("Average:", result_invalid)
Average: 30.0
Error: division by zero. Unable to calculate average.
Average: None

In this example, the try block contains the code that may raise an exception, and the except block catches the ZeroDivisionError if the input list is empty. This prevents the program from crashing, and you can handle the exception by printing an informative error message.

The first call to calculate_average with a valid input list produces the expected average, while the second call with an empty list triggers the ZeroDivisionError, and the program gracefully handles the exception by printing an error message.