Understanding Python Exceptions

In Python, exceptions are raised when an error occurs that disrupts the normal flow of a program. These exceptions can be handled using the try block to test a block of code for errors, and one or more except blocks to catch and handle specific exceptions.

try:
    result = 10 / 0
except ZeroDivisionError as e:
    print(f"Error: Cannot divide by zero. Details: {e}")

Common Built-in Exceptions

Exception TypeDescription
ZeroDivisionErrorRaised when division by zero occurs.
ValueErrorRaised when an operation receives an invalid value.
TypeErrorRaised when an operation is applied to an object of inappropriate type.
FileNotFoundErrorRaised when a file or directory is requested but doesn't exist.
KeyErrorRaised when a dictionary key is not found.
IndexErrorRaised when an index is out of range.
AttributeErrorRaised when an attribute reference or assignment fails.
ImportErrorRaised when an import fails.

Handling Multiple Exceptions

You can handle multiple exceptions in a single except block by specifying a tuple of exception types:

try:
    value = int("abc")
    result = 10 / value
except (ValueError, ZeroDivisionError) as e:
    print(f"Error occurred: {e}")

The else and finally Blocks

The else block runs only if no exceptions are raised in the try block. The finally block is always executed, regardless of whether an exception was raised or caught. This is especially useful for releasing external resources like file handles.

try:
    with open("data.txt", "r") as file:
        content = file.read()
except FileNotFoundError:
    print("The file was not found.")
else:
    print("File read successfully.")
finally:
    print("Execution complete.")

Custom Exceptions

While Python provides many built-in exceptions, you can define your own exception classes to make error handling more expressive and tailored to your application’s logic.

class InvalidUserInputError(Exception):
    """Raised when user input is invalid."""
    pass

def process_input(value):
    if not isinstance(value, int):
        raise InvalidUserInputError("Input must be an integer.")
    return value * 2

try:
    result = process_input("abc")
except InvalidUserInputError as e:
    print(f"Custom error: {e}")

Creating custom exceptions helps in writing clearer code and provides better context when debugging or logging.


Best Practices for Exception Handling

  1. Be Specific with Exceptions: Avoid catching broad exceptions like except Exception unless absolutely necessary. Specific exceptions allow for more precise error handling and debugging.
  1. Don’t Ignore Exceptions: Always handle exceptions in a meaningful way. Swallowing errors with an empty except block can lead to silent failures and hard-to-diagnose issues.
  1. Use try/finally for Resource Cleanup: The finally block is ideal for releasing resources such as file handles, sockets, or database connections.
  1. Log Exceptions: Use Python’s logging module to log exceptions for debugging and monitoring, especially in production code.
  1. Raise Exceptions with Context: When raising exceptions, include useful messages and context to help with debugging.

Real-World Example: Safe File Processing

Let’s implement a function that reads a file and converts each line to an integer, safely handling any potential errors.

import logging

logging.basicConfig(level=logging.ERROR)

def process_file(filename):
    try:
        with open(filename, "r") as file:
            for line in file:
                value = int(line.strip())
                print(f"Processed value: {value}")
    except FileNotFoundError:
        logging.error(f"File {filename} not found.")
    except ValueError as e:
        logging.error(f"Invalid value in file: {e}")
    except Exception as e:
        logging.error(f"An unexpected error occurred: {e}")
    finally:
        print("File processing complete.")

# Example usage
process_file("numbers.txt")

This example demonstrates exception handling in a practical context, including logging and resource management.


Learn more with useful resources