Python - Exception Handling

17.
How do you handle multiple exceptions using a single except block?

In Python, you can handle multiple exceptions using a single except block by specifying multiple exception types within parentheses. This allows you to catch different types of exceptions and handle them in a uniform way.

Here's an example program that demonstrates how to handle multiple exceptions:

def example_function(divisor):
    try:
        result = 10 / divisor

    except (ZeroDivisionError, TypeError) as e:
        print(f"Exception caught: {type(e).__name__} - {e}")

    else:
        print("Division successful. Result:", result)

    finally:
        print("This will always be executed, regardless of exceptions.")

# Example usage
example_function(2)
example_function(0)
example_function("string")

In this example, the example_function attempts to perform a division, and the try block contains the code that might raise an exception. The except block catches both ZeroDivisionError and TypeError. The else block contains code that should run if no exception occurs, and the finally block contains code that will always run.

The output will be:

Division successful. Result: 5.0
Exception caught: ZeroDivisionError - division by zero
This will always be executed, regardless of exceptions.
Exception caught: TypeError - unsupported operand type(s) for /: 'str' and 'int'
This will always be executed, regardless of exceptions.

18.
Discuss the role of the assert statement in debugging and testing code.

The assert statement in Python is primarily used for debugging and testing code. It checks whether a given expression is true, and if it's false, it raises an AssertionError exception, indicating a bug or an issue in the code. It is a useful tool during development to catch and identify logical errors early.

Here's an example program that demonstrates the role of the assert statement:

def divide(a, b):
    assert b != 0, "Division by zero is not allowed"
    return a / b

# Example usage
result = divide(10, 2)
print("Result:", result)

# Uncomment the line below to trigger an assertion error
# result = divide(10, 0)

In this example, the divide function performs division, and the assert statement checks if the divisor (b) is not zero. If the divisor is zero, an AssertionError is raised with the specified message. This helps identify and fix the issue early in development.

The output will be:

Result: 5.0

If you uncomment the line that triggers division by zero, you will get an AssertionError with the specified message.


19.
What is the purpose of the except block without an exception type specified?

In Python, the except block without specifying an exception type is a generic exception handler. It catches and handles any exception that occurs in the corresponding try block, regardless of the specific exception type. While this can be convenient, it's generally considered good practice to handle specific exceptions whenever possible for more targeted error handling.

Here's an example program that demonstrates the use of an except block without a specified exception type:

def divide(a, b):
    try:
        result = a / b
    except:
        print("An error occurred during division")

# Example usage
divide(10, 2)
divide(10, 0)

In this example, the divide function attempts division inside a try block. The except block without an exception type catches any exception that might occur during division and prints a generic error message.

The output will be:

Result: 5.0
An error occurred during division

However, it's recommended to specify the exception type whenever possible for more precise error handling. For example, you might want to handle ZeroDivisionError separately from other types of exceptions.


20.
How can you log exceptions in Python for debugging purposes?

In Python, you can log exceptions for debugging purposes using the logging module. The logging module provides a flexible way to handle and record log messages, including exceptions.

Here's an example program that demonstrates how to log exceptions:

import logging

# Configure logging
logging.basicConfig(filename='example.log', level=logging.DEBUG)

def divide(a, b):
    try:
        result = a / b
    except Exception as e:
        # Log the exception
        logging.exception(f"An error occurred: {e}")

# Example usage
divide(10, 2)
divide(10, 0)

In this example, the divide function attempts division inside a try block. If an exception occurs, the except block logs the exception using logging.exception(). The log messages are written to a file named example.log.

The output in the log file (example.log) will contain information about the exception:

ERROR:root:An error occurred: division by zero
Traceback (most recent call last):
  File "example.py", line 12, in divide
    result = a / b
ZeroDivisionError: division by zero

This log information can be valuable for debugging, as it includes the exception type, the error message, and the traceback.