Python - Debugging

5.
Explain the difference between a syntax error and a runtime error.

In Python, syntax errors and runtime errors are two different types of errors that can occur during the development and execution of a program.

Syntax Error: A syntax error occurs when the structure of the code violates the rules of the Python language. It is detected by the Python interpreter during the parsing phase before the program is executed. Syntax errors prevent the program from running and must be fixed before the code can be executed.

# Example of a syntax error
print("Hello, World!"  # Missing closing parenthesis

Runtime Error: A runtime error occurs during the execution of the program. Unlike syntax errors, runtime errors do not prevent the program from starting, but they cause the program to terminate abruptly when the error is encountered. Common runtime errors include division by zero, accessing an index that is out of bounds, or calling a method on an object that does not have that method.

# Example of a runtime error
numbers = [1, 2, 3]
result = numbers[5]  # IndexError: list index out of range
print(result)

In the first example, a syntax error is introduced by missing the closing parenthesis in the print() statement. This error will be detected during the parsing phase, and the program will not run until the syntax error is fixed.

In the second example, a runtime error occurs when attempting to access an element at index 5 in a list that only has three elements. This error will be encountered during program execution, causing the program to terminate with an IndexError.

File "example.py", line 2
print("Hello, World!"  # Missing closing parenthesis
                          ^
SyntaxError: invalid syntax

File "example.py", line 4
result = numbers[5]  # IndexError: list index out of range
IndexError: list index out of range

6.
How do you use the assert statement for debugging in Python?

The assert statement in Python is used as a debugging aid. It tests a condition, and triggers an error if the condition is not true. It is often used to check assumptions about the state of the program during development.

Here's an example program demonstrating the use of assert:

def calculate_average(numbers):
    assert len(numbers) > 0, "Input list must not be empty"  # Check if the list is not empty

    total = 0
    count = 0

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

    average = total / count
    return 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)  # This line won't be executed due to the assertion error

In this example, the assert statement checks whether the length of the input list numbers is greater than 0. If the condition is false, it raises an AssertionError with the specified error message.

File "example.py", line 16, in calculate_average
assert len(numbers) > 0, "Input list must not be empty"
AssertionError: Input list must not be empty

File "example.py", line 20, in <module>
result_invalid = calculate_average(numbers_invalid)
File "example.py", line 16, in calculate_average
assert len(numbers) > 0, "Input list must not be empty"
AssertionError: Input list must not be empty

In the first example, the input list numbers_valid is not empty, so the program runs successfully. In the second example, the input list numbers_invalid is empty, triggering the AssertionError with the specified error message.


7.
What is the purpose of the logging module in Python?

The logging module in Python provides a flexible framework for emitting log messages from programs. It is used for capturing and recording events that occur during the execution of a program, making it easier to understand its behavior and diagnose issues.

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

import logging

# Configure the logging module
logging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(levelname)s - %(message)s')

def calculate_average(numbers):
    logging.info("Calculating average")

    if not numbers:
        logging.error("Input list is empty. Unable to calculate average.")
        return None

    total = sum(numbers)
    count = len(numbers)

    average = total / count
    logging.debug(f"Average calculated: {average}")
    return average

# Example 1: Valid input
numbers_valid = [10, 20, 30, 40, 50]
result_valid = calculate_average(numbers_valid)
logging.info("Program execution completed.")
print("Average:", result_valid)

# Example 2: Invalid input (empty list)
numbers_invalid = []
result_invalid = calculate_average(numbers_invalid)
logging.info("Program execution completed.")
print("Average:", result_invalid)

In this example, the logging module is configured using basicConfig() to set the logging level to DEBUG and define the log message format. The info(), error(), and debug() functions are then used to log messages at different levels.

2024-02-03 12:00:00,000 - INFO - Calculating average
2024-02-03 12:00:00,000 - DEBUG - Average calculated: 30.0
2024-02-03 12:00:00,000 - INFO - Program execution completed.
Average: 30.0

2024-02-03 12:00:00,000 - INFO - Calculating average
2024-02-03 12:00:00,000 - ERROR - Input list is empty. Unable to calculate average.
2024-02-03 12:00:00,000 - INFO - Program execution completed.
Average: None

The log messages provide information about the program's execution, including when the average is calculated, potential errors, and the completion of the program.


8.
Discuss the concept of exception traceback in Python debugging.

In Python debugging, the concept of an exception traceback provides detailed information about the sequence of function calls and their respective lines of code that led to an exception. It is a valuable tool for identifying the source of errors and understanding the program's flow at the time of the exception.

Here's an example program demonstrating the concept of exception traceback:

def divide_numbers(a, b):
    result = a / b
    return result

def calculate_average(numbers):
    total = sum(numbers)
    count = len(numbers)
    average = divide_numbers(total, count)  # Triggering a ZeroDivisionError
    return average

# Example: Invalid input (empty list)
numbers_invalid = []

try:
    result_invalid = calculate_average(numbers_invalid)
    print("Average:", result_invalid)
except Exception as e:
    # Catching and printing the exception traceback
    import traceback
    traceback_message = traceback.format_exc()
    print(f"An exception occurred: {e}")
    print(traceback_message)

In this example, the calculate_average function attempts to divide the total sum of numbers by the count of numbers. When the input list numbers_invalid is empty, it triggers a ZeroDivisionError.

An exception occurred: division by zero
Traceback (most recent call last):
  File "example.py", line 15, in <module>
    result_invalid = calculate_average(numbers_invalid)
  File "example.py", line 9, in calculate_average
    average = divide_numbers(total, count)  # Triggering a ZeroDivisionError
  File "example.py", line 4, in divide_numbers
    result = a / b
ZeroDivisionError: division by zero

The exception traceback provides a chronological list of function calls and the respective lines of code where the error occurred. This information assists developers in identifying the root cause of the exception and understanding the program's execution flow at the time of the error.