← Back to Tutorials Chapter 12

Errors & Exceptions

Python exception handling flow

Try, Except, Else, Finally

Python uses try/except blocks to handle runtime errors gracefully. The else clause runs when no exception occurs, and finally always executes.

try:
    result = 10 / int(user_input)
except ZeroDivisionError:
    print("Cannot divide by zero!")
except ValueError:
    print("Invalid number!")
else:
    print(f"Result: {result}")
finally:
    print("Execution complete.")

Catching Specific Exceptions

Always catch specific exceptions rather than a bare except:. Common built-in exceptions include ValueError, TypeError, FileNotFoundError, IndexError, and KeyError.

try:
    with open("data.txt") as f:
        data = f.read()
except FileNotFoundError:
    print("File not found. Creating a new one.")
except PermissionError:
    print("Permission denied.")

Raising Exceptions with raise

def withdraw(balance, amount):
    if amount > balance:
        raise ValueError("Insufficient funds")
    return balance - amount

Assertions

The assert statement is used for debugging. It raises AssertionError if the condition is False.

def divide(a, b):
    assert b != 0, "Denominator cannot be zero"
    return a / b
Note: Assertions can be disabled with the -O flag. Do not use them for validation in production.

Custom Exceptions

class InsufficientBalanceError(Exception):
    def __init__(self, balance, amount):
        self.balance = balance
        self.amount = amount
        super().__init__(f"Need {amount}, have {balance}")

raise InsufficientBalanceError(100, 200)

Logging with the logging Module

import logging

logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s - %(levelname)s - %(message)s"
)

logging.debug("Detailed debug info")
logging.info("Operation completed successfully")
logging.warning("Low disk space")
logging.error("Failed to connect")
logging.critical("System is shutting down")

The warnings Module

import warnings

warnings.filterwarnings("ignore")
warnings.warn("This feature is deprecated", DeprecationWarning)

Debugging with pdb

Python's built-in debugger allows you to step through code interactively.

import pdb

def buggy_function(x):
    pdb.set_trace()  # Execution pauses here
    result = x / 0
    return result

In Python 3.7+, you can use the built-in breakpoint() function instead of pdb.set_trace().

def calculate(values):
    breakpoint()  # Opens the debugger
    return sum(values) / len(values)
Tip: Common pdb commands: n (next line), s (step into), c (continue), p var (print variable), l (list source), q (quit).

Exception Chaining

try:
    process_file("data.csv")
except FileNotFoundError as e:
    raise RuntimeError("Cannot start processing") from e
Exercise: Write a function safe_divide(a, b) that handles ZeroDivisionError, TypeError, and logs errors using the logging module. Then write a custom exception ValidationError and use it in a function that validates user input (age between 0-120, name non-empty).