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.
Table of Contents
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?
- Precision: Catch exactly what you mean to catch, nothing more
- Safety: Never accidentally hide critical errors like KeyboardInterrupt
- Professionalism: Senior developers write precise exception handlers
- Debugging: Understanding hierarchy helps you predict which exceptions can occur
- 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) # TrueWhen 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 exit2. 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: BrokenPipeError, ConnectionAbortedError, ConnectionRefusedError, 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 itRule 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 workRule 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 eRule 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 functionNow 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 should2. 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 errors4. 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 eWant 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 callsPattern 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 occurredPattern 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 errorPattern 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 (SystemExit, KeyboardInterrupt). 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:
BaseExceptionis the root—almost never catch it directlyExceptionis 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.
- Order a Code Review – Get feedback on your error handling patterns
- Book a Tutoring Session – One-on-one mentorship for Python mastery
- Read More: 20 Most Common Python Errors in University Projects – Practical error guide
- Read More: How to Use Python's Breakpoint() Like a Pro – Debug like a senior dev
- Read More: Systematic Troubleshooting for Python Assignments – Complete debugging system
Stop guessing. Start handling errors like you know what you're doing.
Tags:
#advanced-python #coding-best-practices #error-handling #exception-hierarchy #python-debugging #python-error-handling #python-exceptions #python-programming #python-tutorial #try-except-pythonRelated 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, 2026How 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, 2026Two 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, 2026Need Coding Help?
Get expert assistance with your programming assignments and projects.