An Introduction to the Art of Computer Programming Using Python in the Age of Generative AI

X. Error and Exception Handling

Errors should never pass silently.
Unless explicitly silenced.
Tim Peters, Zen of Python

Introduction to Error Handling

Error handling ensures that your program can respond gracefully to unexpected situations, such as invalid user input, missing files, or network timeouts. In Python, errors are communicated via exceptions. When an error arises, Python creates an exception object, interrupting the normal program flow. By handling exceptions proactively, you can prevent crashes and improve reliability.

Whether you’re developing simple scripts or large-scale applications, robust exception handling is important. It helps you troubleshoot issues faster and provides a better user experience by offering clear error messages or fallback actions instead of abrupt failures.

Basic Exception Handling

The try and except keywords are used to catch and manage exceptions. If an error occurs within the try block, Python checks the matching except block for that specific exception type, allowing the program to continue.


try:
    result = 10 / 0
except ZeroDivisionError:
    result = "undefined"
print(result)
        

In this example, dividing by zero raises a ZeroDivisionError. The except block catches it, assigns a fallback value, and prevents a crash.

Handling Multiple Exceptions

A single try block can have multiple except blocks to handle different types of errors. This lets you tailor your response according to the specific problem.


try:
    my_list = [1, 2, 3]
    result = my_list[5]
except IndexError:
    result = "Index error"
except Exception as e:
    result = str(e)
print(result)
        

In this code, a list out-of-bounds access triggers IndexError, handled separately from other generic exceptions.

The Else and Finally Clauses

Python provides optional else and finally blocks to further refine error handling:


try:
    result = 10 / 2
except ZeroDivisionError:
    print("Divided by zero!")
else:
    print("Division successful!")
finally:
    print("This runs no matter what.")
        

Raising Exceptions

You can use the raise statement to manually throw an exception. This is useful if you want to signal a specific error condition or enforce certain assumptions in your code.


def divide(a, b):
    if b == 0:
        raise ValueError("You can't divide by zero!")
    return a / b

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

By raising a ValueError, you clearly highlight the problematic condition. This approach makes the code’s intent more obvious to anyone reading or using this function.

Custom Exception Classes

For more complex applications, you may define your own exceptions that extend Python’s built-in Exception class. Custom exceptions help you handle domain-specific errors more precisely and distinguish them from generic Python errors.


class NegativeValueError(Exception):
    pass

def sqrt(value):
    if value < 0:
        raise NegativeValueError("Cannot compute the square root of a negative number!")
    return value ** 0.5

try:
    result = sqrt(-9)
except NegativeValueError as nve:
    result = str(nve)
print(result)
        

Using custom exception names like OutOfStockError can make error handling more self-documenting in specialized domains.

Logging Exceptions

When exceptions occur, it’s often helpful to log them for troubleshooting or auditing. Python’s logging module provides a consistent way to track errors (and other events) in the console, files, or external logging services.


import logging

logging.basicConfig(level=logging.ERROR)

def divide(a, b):
    try:
        return a / b
    except ZeroDivisionError as e:
        logging.error("Attempted to divide by zero.", exc_info=True)
        return "undefined"

result = divide(10, 0)
print(result)
        

The exc_info=True parameter includes the traceback in your log, making it easier to debug. You can adjust logging to categorize different severity levels (INFO, WARNING, ERROR, etc.).

Prompting Generative AI for Meaningful Error Handling

Large Language Models (LLMs) can help you design robust error handling approaches. By outlining the kinds of problems you anticipate—like file system issues, network timeouts, or user input validation—you can request example code that demonstrates exception handling or Python best practices.

Example Prompt:
Generate a Python function that reads from a file and handles file-related errors gracefully.

Resulting AI-generated code:


def read_file(file_path):
    try:
        with open(file_path, 'r') as file:
            content = file.read()
        return content
    except FileNotFoundError:
        return "File not found."
    except PermissionError:
        return "Permission denied."
    except Exception as e:
        return f"An error occurred: {str(e)}"

file_content = read_file("example.txt")
print(file_content)
        

You can adapt AI-generated snippets to your specific needs—for instance, adding retries, re-raising exceptions in higher-level handlers, or combining file and network error management in a single function. Always test final solutions in real or simulated error scenarios before deployment.

Back to Home