3.5 Exception Handling (try, except, finally)

In Python, errors and exceptions can occur during the execution of a program, and if they are not handled properly, they can cause the program to crash. Exception handling allows you to manage errors gracefully and prevent your program from failing unexpectedly. Python provides a built-in mechanism for handling exceptions using the try, except, else, and finally blocks. In this section, we'll explore how to use these blocks to handle exceptions effectively.


3.5.1 What is an Exception?

An exception is an error that occurs during the execution of a program. When an error is encountered, Python stops the program and raises an exception. If the exception is not handled, the program will terminate and display a traceback (error message).

Common exceptions in Python include:

  • ZeroDivisionError: Raised when dividing by zero.
  • TypeError: Raised when an operation or function is applied to an object of an inappropriate type.
  • ValueError: Raised when a function receives an argument of the right type but inappropriate value.
  • FileNotFoundError: Raised when trying to open a file that doesn’t exist.
  • IndexError: Raised when trying to access an invalid index in a sequence.

Example of an Unhandled Exception:

x = 10 / 0  # Raises ZeroDivisionError

Output:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ZeroDivisionError: division by zero

In this case, the program crashes due to an unhandled ZeroDivisionError.


3.5.2 The try and except Blocks

The try and except blocks are used to handle exceptions. Code that might raise an exception is placed inside the try block, and the code to handle the exception is placed inside the except block.

Syntax:

try:
    # Code that may raise an exception
except ExceptionType:
    # Code to handle the exception

Example 1: Handling a ZeroDivisionError:

try:
    result = 10 / 0
except ZeroDivisionError:
    print("Error: Cannot divide by zero!")

Output:

Error: Cannot divide by zero!

In this example, the ZeroDivisionError is caught by the except block, and the program does not crash. Instead, it prints an error message and continues.

Example 2: Handling Multiple Exceptions:

You can handle multiple exceptions by specifying different except blocks for different error types.

try:
    x = int(input("Enter a number: "))
    result = 10 / x
except ZeroDivisionError:
    print("Error: Division by zero is not allowed.")
except ValueError:
    print("Error: Invalid input. Please enter a valid number.")

Output (if user enters a string):

Error: Invalid input. Please enter a valid number.

In this example, if the user enters 0, a ZeroDivisionError is raised, and if the user enters a non-numeric value, a ValueError is raised. Each error is handled by the appropriate except block.

Example 3: Catching Any Exception

You can catch any exception using a generic except block without specifying an exception type.

try:
    result = 10 / 0
except:
    print("An error occurred.")

Output:

An error occurred.

While this approach is simple, it’s generally better to catch specific exceptions so that you can handle different errors appropriately.


3.5.3 The else Block

The else block is used to define code that should run only if no exceptions were raised in the try block. If an exception occurs, the else block will not be executed.

Syntax:

try:
    # Code that may raise an exception
except ExceptionType:
    # Code to handle the exception
else:
    # Code to execute if no exception occurs

Example:

try:
    result = 10 / 2
except ZeroDivisionError:
    print("Error: Division by zero.")
else:
    print(f"Result is: {result}")

Output:

Result is: 5.0

In this case, since no exception occurs, the code inside the else block is executed, and the result is printed.


3.5.4 The finally Block

The finally block is used to define code that will run no matter what happens—whether an exception was raised or not. This block is often used for cleanup operations, such as closing files or releasing resources.

Syntax:

try:
    # Code that may raise an exception
except ExceptionType:
    # Code to handle the exception
finally:
    # Code that will run no matter what

Example:

try:
    result = 10 / 2
except ZeroDivisionError:
    print("Error: Division by zero.")
finally:
    print("This block runs no matter what.")

Output:

This block runs no matter what.
Result is: 5.0

In this example, the finally block runs whether or not an exception occurs. This is useful when you need to ensure certain actions are always performed, like closing a file.


3.5.5 Raising Exceptions Manually

You can manually raise exceptions in your code using the raise statement. This can be useful for signaling errors when specific conditions are met in your program.

Syntax:

raise ExceptionType("Error message")

Example:

def divide(a, b):
    if b == 0:
        raise ValueError("Cannot divide by zero!")
    return a / b

try:
    result = divide(10, 0)
except ValueError as e:
    print(e)

Output:

Cannot divide by zero!

In this example, the ValueError is manually raised inside the divide() function when an attempt is made to divide by zero.


3.5.6 Exception Object (as)

When an exception is caught, Python creates an exception object that contains information about the error. You can capture this object using the as keyword and print or log the error details.

Syntax:

try:
    # Code that may raise an exception
except ExceptionType as e:
    print(e)

Example:

try:
    result = 10 / 0
except ZeroDivisionError as e:
    print(f"An error occurred: {e}")

Output:

An error occurred: division by zero

In this case, the exception message ("division by zero") is stored in the variable e, which can be used to display detailed error information.


3.5.7 Catching Multiple Exceptions with a Single except Block

You can catch multiple types of exceptions in a single except block by using a tuple of exception types.

Syntax:

try:
    # Code that may raise an exception
except (ExceptionType1, ExceptionType2):
    # Code to handle either exception

Example:

try:
    x = int(input("Enter a number: "))
    result = 10 / x
except (ZeroDivisionError, ValueError):
    print("Error: Invalid input or division by zero.")

Output (if user enters 0 or non-numeric input):

Error: Invalid input or division by zero.

In this example, both ZeroDivisionError and ValueError are handled by the same except block.


3.5.8 Best Practices for Exception Handling

  1. Use finally for Cleanup: Always use the finally block for resource cleanup, like closing files or releasing resources.
  2. Don’t Suppress Exceptions: Avoid using bare except blocks that catch all exceptions without handling them properly. This can hide bugs and make debugging difficult.
  3. Raise Exceptions When Necessary: Raise exceptions when you need to signal that an operation has failed. Use custom error messages to provide context for the error.

Be Specific: Catch specific exceptions rather than using a generic except block. This makes debugging easier and helps avoid catching unintended exceptions.Example:

try:
    result = 10 / 0
except ZeroDivisionError:
    print("Division by zero error.")

3.5.9 Summary

  • try block: Contains code that may raise an exception.
  • except block: Handles the exception if one is raised.
  • else block: Executes if no exception occurs.
  • finally block: Runs whether an exception occurs or not, often used for cleanup tasks.
  • raise statement: Used to manually raise exceptions when needed.
  • Exception object (as): Captures the

error message or details of the exception for use in error handling.

Effective exception handling allows your Python programs to manage errors gracefully and continue running, even when things go wrong.