Python February 26, 2026 10 min read 38 views

Python Exception Hierarchy Explained

Ever wondered why Python has so many different exceptions? Learn the complete exception hierarchy, when to use which exception, and how to write better error-handling code that impresses professors.

Python Exception Hierarchy Explained: Stop Catching Everything

Look at this code:

try:
    risky_operation()
except Exception as e:
    print("Something went wrong:", e)

Seems fine, right? It catches any error, prints a message, and the program continues. What's the problem?

The problem is that this code catches everything—including errors you should never catch. KeyboardInterrupt (when the user presses Ctrl+C)? Caught. SystemExit (when the program should exit)? Caught. MemoryError? Also caught. Your program will just keep running when it should stop.

Most students learn try-except blocks but never learn the hierarchy of exceptions. And that's how innocent-looking code becomes dangerous.

Why Exception Hierarchy Matters

Python's exceptions aren't just random error types—they're organized in a strict family tree. Understanding this tree transforms how you handle errors.

Why does this matter for your grades and projects?

  1. Precision: Catch exactly what you mean to catch, nothing more
  2. Safety: Never accidentally hide critical errors like KeyboardInterrupt
  3. Professionalism: Senior developers write precise exception handlers
  4. Debugging: Understanding hierarchy helps you predict which exceptions can occur
  5. Code quality: Proper exception handling separates A students from B students

Let's explore the entire exception family tree.

BaseException
├── SystemExit
├── KeyboardInterrupt
├── GeneratorExit
└── Exception
    ├── StopIteration
    ├── StopAsyncIteration
    ├── ArithmeticError
    │   ├── FloatingPointError
    │   ├── OverflowError
    │   └── ZeroDivisionError
    ├── AssertionError
    ├── AttributeError
    ├── BufferError
    ├── EOFError
    ├── ImportError
    │   └── ModuleNotFoundError
    ├── LookupError
    │   ├── IndexError
    │   └── KeyError
    ├── MemoryError
    ├── NameError
    │   └── UnboundLocalError
    ├── OSError
    │   ├── BlockingIOError
    │   ├── ChildProcessError
    │   ├── ConnectionError
    │   │   ├── BrokenPipeError
    │   │   ├── ConnectionAbortedError
    │   │   ├── ConnectionRefusedError
    │   │   └── ConnectionResetError
    │   ├── FileExistsError
    │   ├── FileNotFoundError
    │   ├── InterruptedError
    │   ├── IsADirectoryError
    │   ├── NotADirectoryError
    │   ├── PermissionError
    │   ├── ProcessLookupError
    │   └── TimeoutError
    ├── ReferenceError
    ├── RuntimeError
    │   ├── NotImplementedError
    │   └── RecursionError
    ├── SyntaxError
    │   └── IndentationError
    │       └── TabError
    ├── SystemError
    ├── TypeError
    ├── ValueError
    │   └── UnicodeError
    │       ├── UnicodeDecodeError
    │       ├── UnicodeEncodeError
    │       └── UnicodeTranslateError
    └── Warning
        ├── DeprecationWarning
        ├── FutureWarning
        ├── UserWarning
        ├── SyntaxWarning
        ├── RuntimeWarning
        └── ... (and more warning types)

This looks overwhelming, but the structure is logical. Let's break it down.


Step-by-Step Breakdown: Understanding the Hierarchy

Level 1: BaseException — The Root of All Evil

At the very top sits BaseException. Every single exception in Python inherits from this class.

# All of these are true
isinstance(ZeroDivisionError(), BaseException)  # True
isinstance(KeyboardInterrupt(), BaseException)  # True
isinstance(ValueError(), BaseException)        # True

When to use it: Almost never. Catching BaseException catches literally everything, including system exits.

# DON'T DO THIS
try:
    run_program()
except BaseException:
    print("Caught everything")  # This catches Ctrl+C too!

Level 2: The Three Direct Children

BaseException has four direct children, but three are special:

1. SystemExit

Raised when sys.exit() is called. The program is intentionally exiting.

import sys

try:
    sys.exit("Goodbye cruel world")
except SystemExit:
    print("Caught exit!")  # This runs, then program exits anyway
    # You can't actually stop the exit

2. KeyboardInterrupt

Raised when the user presses Ctrl+C. The user wants the program to stop.

try:
    while True:
        print("Working...")
except KeyboardInterrupt:
    print("\nUser stopped the program")
    # Clean up, then exit
    sys.exit(0)

3. GeneratorExit

Raised when a generator is closed. Internal use only—you'll rarely see this.

4. Exception — Where 99% of Your Code Lives

Everything else inherits from Exception. This is the class you'll use 99% of the time.

# This is the standard pattern
try:
    risky_operation()
except Exception:  # Catches everything except SystemExit and KeyboardInterrupt
    print("Something went wrong")

Level 3: The Major Exception Families

Under Exception, Python organizes errors into logical families.

ArithmeticError: Math Gone Wrong

Parent of all math-related errors:

# ZeroDivisionError
try:
    result = 10 / 0
except ZeroDivisionError:
    print("Can't divide by zero!")

# OverflowError
try:
    result = 10.0 ** 1000000
except OverflowError:
    print("Number too large!")

# FloatingPointError (rare - hardware dependent)

LookupError: When You Can't Find Things

Parent of indexing and key errors:

# IndexError - sequence index out of range
items = [1, 2, 3]
try:
    print(items[10])
except IndexError:
    print("Index doesn't exist")

# KeyError - dictionary key not found
data = {"name": "Alice"}
try:
    print(data["age"])
except KeyError:
    print("Key not found")

The beauty of hierarchy: you can catch either specific errors or all lookup errors:

# Catch specific
try:
    value = data[key]
except KeyError:
    print(f"Key {key} not found")

# Catch any lookup error (IndexError or KeyError)
try:
    value = sequence[index]
except LookupError:
    print("Couldn't find that item")

OSError: Everything Related to the Operating System

This family is huge because so many things can go wrong with files, networks, and processes:

# FileNotFoundError (most common)
try:
    with open("missing.txt", "r") as f:
        data = f.read()
except FileNotFoundError:
    print("File doesn't exist")

# PermissionError
try:
    with open("/etc/shadow", "r") as f:  # System file you can't read
        data = f.read()
except PermissionError:
    print("Don't have permission")

# ConnectionError family
import requests
try:
    response = requests.get("http://nonexistent-site.com")
except ConnectionError:
    print("Couldn't connect to server")

Notice that ConnectionError has four children: BrokenPipeErrorConnectionAbortedErrorConnectionRefusedError, and ConnectionResetError. You can catch any of them specifically or catch the parent ConnectionError for all network issues.

RuntimeError: General "Something Went Wrong"

When nothing else fits, it's a RuntimeError:

# RecursionError (child of RuntimeError)
def infinite_recursion():
    return infinite_recursion()

try:
    infinite_recursion()
except RecursionError:
    print("Too much recursion!")

# NotImplementedError (for abstract methods)
class BaseClass:
    def must_implement(self):
        raise NotImplementedError("Subclasses must implement this")

Level 4: The Most Common Specific Exceptions

These are the exceptions you'll encounter daily:

TypeError: Operation on Wrong Type

try:
    result = "5" + 3  # String + integer
except TypeError:
    print("Can't combine string and integer")

ValueError: Right Type, Wrong Value

try:
    number = int("hello")  # String can't be converted to int
except ValueError:
    print("Invalid conversion")

try:
    import math
    math.sqrt(-1)  # Square root of negative number
except ValueError:
    print("Can't take sqrt of negative number")

AttributeError: Object Lacks Attribute

 

class Person:
    def __init__(self, name):
        self.name = name

p = Person("Alice")
try:
    p.age  # Doesn't exist
except AttributeError:
    print("No age attribute")

ImportError and ModuleNotFoundError

 

try:
    import requests  # Might not be installed
except ModuleNotFoundError:
    print("Install requests with: pip install requests")

try:
    from math import nonexistent_function
except ImportError:
    print("That function doesn't exist in math")

Writing Professional Exception Handlers

Now that you understand the hierarchy, here's how to write exception handlers like a pro.

Rule 1: Be Specific

# BAD - catches everything
try:
    with open("data.txt", "r") as f:
        data = f.read()
    number = int(data)
    result = 100 / number
except Exception:
    print("Something went wrong")  # What? Why? No idea.

# GOOD - handle each possible error appropriately
try:
    with open("data.txt", "r") as f:
        data = f.read()
    
    number = int(data)
    result = 100 / number
    
except FileNotFoundError:
    print("Create data.txt first")
    # Maybe create default file
except PermissionError:
    print("Can't read data.txt - check permissions")
except ValueError:
    print("data.txt doesn't contain a valid number")
except ZeroDivisionError:
    print("Number can't be zero")
except Exception as e:
    print(f"Unexpected error: {e}")  # Catch unexpected but log it

Rule 2: Use Hierarchy for Grouped Handling

When multiple errors need the same handling, catch their parent:

# Instead of:
try:
    with open("data.txt", "r") as f:
        content = f.read()
except FileNotFoundError:
    print("File issue")
except PermissionError:
    print("File issue")
except IsADirectoryError:
    print("File issue")

# Do this:
try:
    with open("data.txt", "r") as f:
        content = f.read()
except OSError as e:  # Catches ALL file-related errors
    print(f"File issue: {e}")

Rule 3: Don't Catch What You Can't Handle

 

# BAD - catching KeyboardInterrupt accidentally
try:
    while True:
        process_data()
except Exception:  # Catches everything except BaseException children
    print("Stopping")  # Ctrl+C won't work!

# GOOD - let critical exceptions through
try:
    while True:
        process_data()
except (FileNotFoundError, PermissionError) as e:
    print(f"File error: {e}")
# KeyboardInterrupt will still work

Rule 4: Preserve the Traceback

When you catch and re-raise, preserve the original error information:

def process_file(filename):
    try:
        with open(filename, 'r') as f:
            return f.read()
    except FileNotFoundError as e:
        # Add context but keep original error
        raise FileNotFoundError(f"Couldn't process {filename}") from e

Rule 5: Create Custom Exceptions for Your Project

For larger projects, create your own exception hierarchy:

class StudentRecordError(Exception):
    """Base exception for student record system"""
    pass

class StudentNotFoundError(StudentRecordError):
    """Raised when a student doesn't exist"""
    pass

class DuplicateStudentError(StudentRecordError):
    """Raised when adding a student that already exists"""
    pass

class InvalidGradeError(StudentRecordError):
    """Raised when grade is invalid (e.g., >100 or <0)"""
    pass

# Usage
def add_grade(student_id, grade):
    if grade < 0 or grade > 100:
        raise InvalidGradeError(f"Grade {grade} must be between 0-100")
    # Rest of function

Now users of your code can catch specifically:

try:
    add_grade("12345", 105)
except InvalidGradeError:
    print("Please enter a valid grade")
except StudentRecordError:
    print("Some other student record error")

 

Exception hierarchy confusing? Master error handling in one tutoring session. Book a session now and learn professional patterns that impress professors.


Common Mistakes with Exception Hierarchy

1. Catching BaseException

# NEVER DO THIS
try:
    run_app()
except BaseException:
    # This catches SystemExit and KeyboardInterrupt!
    pass  # Program won't exit when it should

2. Using Bare Except

# BAD - same as catching BaseException
try:
    risky()
except:
    print("Error")

# GOOD - be specific
try:
    risky()
except ValueError:
    print("Value error")

3. Catching Exception Too Broadly

# BAD - hides unexpected errors
try:
    data = process_user_input(user_input)
except Exception:
    data = get_default()  # Even if it's a KeyboardInterrupt!

# GOOD - specific
try:
    data = process_user_input(user_input)
except ValueError:
    data = get_default()  # Only for expected value errors

4. Ignoring Exception Order

# BAD - parent first catches everything
try:
    x = int("abc")
except Exception:
    print("Caught by Exception")
except ValueError:  # Never runs!
    print("Caught by ValueError")

# GOOD - specific first, general last
try:
    x = int("abc")
except ValueError:
    print("Value error")  # This runs
except Exception:
    print("Some other error")

5. Not Using Exception Chaining

# BAD - lose original error context
def load_config():
    try:
        with open('config.json') as f:
            return json.load(f)
    except FileNotFoundError:
        raise Exception("Config missing")  # Original traceback lost!

# GOOD - preserve context
def load_config():
    try:
        with open('config.json') as f:
            return json.load(f)
    except FileNotFoundError as e:
        raise Exception("Config missing") from e

 

Want to master professional error handling? Our advanced Python course covers exceptions, debugging, and testing. Join the waitlist for our next cohort.


Exception Handling Patterns by Situation

Pattern 1: Retry Logic

import time
from typing import Callable

def retry_on_error(func: Callable, max_retries: int = 3, delay: float = 1.0):
    """Retry a function if it raises an exception."""
    for attempt in range(max_retries):
        try:
            return func()
        except (ConnectionError, TimeoutError) as e:
            if attempt == max_retries - 1:
                raise  # Last attempt, re-raise
            print(f"Attempt {attempt + 1} failed. Retrying in {delay}s...")
            time.sleep(delay)
    
    # This pattern is great for network calls

Pattern 2: Cleanup with Finally

 

# Guarantee cleanup happens
file = None
try:
    file = open("data.txt", "r")
    data = file.read()
    result = process(data)
except FileNotFoundError:
    print("File missing")
finally:
    if file:
        file.close()  # This runs even if an error occurred

Pattern 3: Context Managers (Even Better)

# Using 'with' is cleaner than try-finally
try:
    with open("data.txt", "r") as file:
        data = file.read()
        result = process(data)
except FileNotFoundError:
    print("File missing")
# File automatically closes even if process() raises error

Pattern 4: EAFP vs LBYL

Python prefers "Easier to Ask for Forgiveness than Permission":

# LBYL (Look Before You Leap) - Less Pythonic
if os.path.exists("data.txt"):
    with open("data.txt", "r") as f:
        data = f.read()
else:
    data = ""

# EAFP (Easier to Ask for Forgiveness) - Pythonic
try:
    with open("data.txt", "r") as f:
        data = f.read()
except FileNotFoundError:
    data = ""

EAFP is preferred because:

  • It's faster (no extra system call to check existence)
  • It's thread-safe (file could be deleted between check and open)
  • It handles all errors, not just the one you checked for

FAQ: Exception Hierarchy Questions

Q: Why does Python have so many exception types?
A: Each exception type tells you exactly what went wrong. FileNotFoundError is more helpful than "something went wrong with the file."

Q: When should I create a custom exception?
A: Create custom exceptions when you're building a library or large project where users need to handle your errors specifically. For small scripts, built-in exceptions are fine.

Q: What's the difference between Exception and BaseException?
A: BaseException includes system-exiting exceptions (SystemExitKeyboardInterrupt). Exception includes everything else—the errors your program should handle.

Q: Should I ever catch KeyboardInterrupt?
A: Yes, if you need to clean up before exiting (closing files, saving state). Just make sure the program still exits afterward.

Q: How do I know which exception might be raised?
A: Check the documentation. For built-in functions, the docs list possible exceptions. For your code, you decide what to raise.

Q: Can I catch multiple exceptions in one line?
A: Yes: except (ValueError, TypeError) as e:

Q: What happens if I don't catch an exception?
A: The program crashes and prints the traceback. Sometimes that's what you want—better than continuing with invalid state.

Q: Is it bad to use bare except:?
A: Yes. It catches everything including SystemExit and KeyboardInterrupt. Always specify exception types.

Q: How do I re-raise the same exception?
A: Use raise with no arguments inside the except block. This preserves the original traceback.

Q: What's the point of the Warning hierarchy?
A: Warnings notify about potential issues without stopping execution. Use warnings.warn() for deprecations or non-critical problems.


The Bottom Line: Handle Errors Like a Pro

Understanding Python's exception hierarchy transforms you from a beginner who catches everything to a professional who handles errors with precision.

Key takeaways:

  • BaseException is the root—almost never catch it directly
  • Exception is your go-to parent for most error handling
  • Be specific: catch exactly what you expect
  • Use hierarchy: catch parents for grouped handling
  • Create custom exceptions for your projects
  • Always preserve error context with from e

Ready to write production-quality Python code?

Mastering exceptions is just one part of becoming a professional Python developer. Let us help you level up.

Stop guessing. Start handling errors like you know what you're doing.


Related Posts

Binary Search Explained: Algorithm, Examples, & Edge Cases

Master the binary search algorithm with clear, step-by-step examples. Learn how to implement efficient searches in sorted arrays, avoid common …

Mar 11, 2026
How to Approach Hard LeetCode Problems | A Strategic Framework

Master the mental framework and strategies to confidently break down and solve even the most challenging LeetCode problems.

Mar 06, 2026
Two Pointer Technique | Master Array Problems in 8 Steps

Master the two-pointer technique to solve complex array and string problems efficiently. This guide breaks down patterns, provides step-by-step examples, …

Mar 11, 2026

Need Coding Help?

Get expert assistance with your programming assignments and projects.