DevHireLab
Tutorials
BootcampProblemsCode SimulatorAI InterviewSoonContact
DevHireLab
Tutorials
BootcampProblemsCode SimulatorAI InterviewSoonContact
Getting Started
01
What is Python and Why Learn It in 2026
02
Installing Python on Windows, Mac, and Linux
03
Setting up VS Code / PyCharm for Python
04
Understanding the Python Interpreter and REPL
05
Writing and Running Your First Python Program
06
Python Syntax and Indentation Rules
Variables and Data Types
01
Variables and Naming Conventions in Python
02
Numbers in Python (int, float, complex)
03
Strings and String Methods in Python
04
Booleans in Python
05
Type Conversion / Casting in Python
06
Understanding the None Type in Python
Operators
01
Arithmetic Operators in Python
02
Comparison Operators in Python
03
Logical Operators in Python
04
Assignment Operators in Python
05
Bitwise Operators in Python
06
Identity (is) and Membership (in) Operators
07
Walrus Operator (:=) in Python
Input, Output & Strings
01
input() and print() Functions in Python
02
f-strings and String Formatting in Python
03
String Slicing and Indexing in Python
04
Common String Methods (split, join, strip, replace)
05
Escape Characters in Python Strings
Control Flow
01
if, elif, else Statements in Python
02
Nested Conditionals in Python
03
for Loops in Python
04
while Loops in Python
05
break, continue, and pass Statements
06
Loop with else Clause in Python
07
match-case (Structural Pattern Matching)
Data Structures
01
Python Lists: Creation, Indexing, Slicing, Methods
02
Python Tuples and Immutability
03
Python Sets and Set Operations
04
Python Dictionaries: Keys, Values, and Methods
05
List Comprehensions in Python
06
Dictionary & Set Comprehensions in Python
07
Working with Nested Lists and Dictionaries
Functions
01
Defining and Calling Functions in Python
02
Function Arguments: Positional, Keyword, Default
03
*args and **kwargs in Python
04
Return Statements in Python Functions
05
Lambda Functions in Python
06
Recursion in Python
07
Variable Scope: Local, Global, Nonlocal
08
Docstrings and Function Annotations
Functional Programming Basics
01
map(), filter(), and reduce() in Python
02
Closures in Python Explained
03
Python Decorators: Basic to Advanced
04
Generators and yield in Python
Object-Oriented Programming (OOP)
01
Classes and Objects in Python
02
The init Constructor in Python
03
Instance vs Class Variables in Python
04
Inheritance in Python (Single, Multiple, Multilevel)
05
Polymorphism in Python
06
Encapsulation in Python
07
Abstraction in Python with the abc Module
08
Magic/Dunder Methods in Python
09
Static and Class Methods in Python
10
The @property Decorator in Python
11
Python Dataclasses Explained
Error Handling
01
try, except, finally in Python
02
Handling Multiple Exceptions in Python
03
Raising Custom Exceptions in Python
04
Understanding Python's Exception Hierarchy
05
Assertions in Python
File Handling
01
Reading and Writing Text Files in Python
02
Working with the with Statement in Python
03
Working with CSV Files in Python
04
Working with JSON Files in Python
05
Working with Directories (os, pathlib) in Python
Modules and Packages
01
Importing Built-in Modules in Python
02
Creating Your Own Modules in Python
03
Understanding Packages and init.py
04
Python Standard Library Overview
05
Installing Packages with pip
Advanced Core Concepts
01
Iterators and Iterables in Python
02
Context Managers in Python (with, contextlib)
03
Multithreading Basics in Python
04
Multiprocessing Basics in Python
05
Asyncio Basics: async/await in Python
06
Memory Management and Garbage Collection in Python
07
Type Hints in Python (typing module)

Understanding Python's Exception Hierarchy

Every exception you've caught throughout this series — ValueError, KeyError, ZeroDivisionError — belongs to a structured python exception hierarchy, not a flat, unrelated list. Understanding the BaseException vs Exception distinction, and the branches in between, directly shapes how broadly or narrowly your except blocks actually catch errors.

Introduction: Exceptions Form a Family Tree

Every exception in Python is an instance of a class, and that class inherits — directly or indirectly — from BaseException. This means Python's exceptions form a structured inheritance tree, not a flat, disconnected list of error types.

Why this matters practically

Because exceptions inherit from one another, catching a general exception type automatically catches all of its more specific subclasses too. except OSError: catches FileNotFoundError, PermissionError, ConnectionError, and every other OSError subclass — you don't need to list each one individually, because they're all, structurally, a kind of OSError.

A preview

This article walks the tree from the root (BaseException) down through the major branches, to the specific, familiar exception types you've already been catching throughout this series — and covers exactly how that structure shapes the ordering rules for except blocks.

The Root: BaseException vs. Exception

BaseException: the true root

BaseException sits at the very top of Python's exception hierarchy — every other exception, without exception (no pun intended), inherits from it directly or indirectly.

The special cases directly beneath it

A small handful of exceptions sit directly under BaseException, deliberately kept outside of Exception:

  • SystemExit — raised when sys.exit() is called, signaling the program should terminate.

  • KeyboardInterrupt — raised when a user presses Ctrl+C.

  • GeneratorExit — raised inside a generator when it's closed.

This separation is deliberate, and genuinely important: keeping these three outside of Exception means that catching Exception broadly — a common, generally reasonable pattern — won't accidentally swallow a user's Ctrl+C, or interfere with a normal, intentional program exit. This is exactly the reasoning behind the "avoid bare except:" guidance covered in the earlier try/except/finally article: a bare except: catches BaseException and everything beneath it, including these three signals that almost never should be silently caught by ordinary application error-handling code.

Exception: the practical base for everything else

Exception is the base class for almost all non-fatal, recoverable errors — the ones you actually want to catch and handle in everyday code. It's also the correct parent class for your own custom exceptions, exactly as covered in the earlier custom exceptions article. For nearly all practical purposes, Exception — not BaseException — is the root you should be thinking about day to day.

Touring the Major Branches

Several intermediate base classes group related, specific exceptions together, and it's worth knowing the major ones.

ArithmeticError

Parent of exceptions related to numeric computation problems:

  • ZeroDivisionError — division or modulo by zero.

  • OverflowError — a numeric result too large to represent.

  • FloatingPointError — a floating-point operation failure (rare in practice, since Python's default float handling doesn't typically raise this).

LookupError

Parent of exceptions raised when a lookup (by index or key) fails:

  • IndexError — an invalid position in a sequence, covered in the earlier lists and string slicing articles.

  • KeyError — a missing key in a dictionary, covered in the earlier dictionaries article.

OSError

Parent of exceptions related to system-level, operating-environment problems — file access, network connections, and similar:

  • FileNotFoundError — a file that doesn't exist.

  • PermissionError — insufficient permissions to perform an operation.

  • ConnectionError (itself a parent of further subtypes like ConnectionResetError and ConnectionRefusedError) — network connection problems.

A visual excerpt of the tree
BaseException
├── SystemExit
├── KeyboardInterrupt
├── GeneratorExit
└── Exception
    ├── ArithmeticError
    │   ├── ZeroDivisionError
    │   ├── OverflowError
    │   └── FloatingPointError
    ├── LookupError
    │   ├── IndexError
    │   └── KeyError
    ├── OSError
    │   ├── FileNotFoundError
    │   ├── PermissionError
    │   └── ConnectionError
    │       ├── ConnectionResetError
    │       └── ConnectionRefusedError
    ├── ValueError
    ├── TypeError
    └── ... (many more)

This is a deliberately trimmed excerpt — the real tree is considerably larger — but it shows the essential structure: FileNotFoundError is genuinely a kind of OSError, and ZeroDivisionError is genuinely a kind of ArithmeticError, not just a coincidentally similar, unrelated type.

Why these intermediate classes exist

These grouping classes let code catch an entire category of related errors without listing every specific subtype individually:

try:
    with open("data.txt") as f:
        c f.read()
except OSError as e:
    print(f"A file or system-level problem occurred: {e}")

This single except OSError: correctly catches FileNotFoundError, PermissionError, and any other OSError subclass — genuinely useful when your actual handling logic ("something went wrong accessing this file or resource") is the same regardless of exactly which specific OSError subtype occurred.

Why Hierarchy Order Matters in except Blocks

The practical rule

This connects directly to the hierarchy structure covered above: when using multiple except clauses, specific exceptions must be listed before general ones — Python checks except blocks top to bottom and stops at the very first class that matches, exactly as covered in the earlier multiple exceptions article. Because subclasses match their parent class's except too, a general class listed first will silently intercept everything, including the more specific exceptions you meant to handle separately.

A concrete example: correct order
try:
    value = int("abc")
except ValueError:
    print("Specifically caught a ValueError")
except Exception:
    print("Caught some other, more general exception")
Specifically caught a ValueError

ValueError, listed first and being more specific, correctly catches the error before Python ever reaches the broader except Exception: clause below it.

What silently breaks if the order is reversed
try:
    value = int("abc")
except Exception:
    print("Caught some other, more general exception")
except ValueError:
    print("Specifically caught a ValueError")
Caught some other, more general exception

Because ValueError is a subclass of Exception, the broader except Exception: clause — now listed first — matches immediately, and Python never even reaches the more specific except ValueError: clause below it. This isn't an error or a warning — it's a silent, easy-to-miss logic bug, where your intended specific handling simply never runs, because a broader clause earlier in the chain quietly absorbed it first. This is exactly the same "order matters" principle covered for elif chains in the earlier conditionals article, applied here specifically to exception class hierarchies instead of plain value comparisons.

Reinforcing: avoid catching BaseException directly

Worth restating clearly here, now that the full hierarchy context is in view: catching BaseException directly in regular application code is discouraged for exactly the same reason a bare except: is discouraged — it's simply too broad, silently catching SystemExit and KeyboardInterrupt right alongside everything else, interfering with a user's ability to cleanly stop your program or exit it intentionally.

Exploring the Hierarchy Yourself, and Modern Additions

Using inspect to see the live tree

Rather than memorizing the hierarchy, you can inspect it programmatically, directly from a running Python session, using __subclasses__() recursively:

def print_exception_tree(cls, indent=0):
    print("  " * indent + cls.__name__)
    for subclass in cls.__subclasses__():
        print_exception_tree(subclass, indent + 1)

print_exception_tree(BaseException)

This walks the actual, currently-loaded exception hierarchy in your running Python environment — genuinely useful for confirming exactly where a specific exception type sits, or for discovering exception types you might not already be aware of, rather than relying purely on documentation or memory. Python's inspect module also provides inspect.getclasstree(), which can build a similarly structured, nested representation of a class hierarchy programmatically, if you need it in a different format for further processing.

Newer additions: ExceptionGroup and BaseExceptionGroup

As covered in the earlier multiple exceptions article, Python 3.11 introduced ExceptionGroup (and its counterpart, BaseExceptionGroup) for representing several unrelated exceptions raised together as a single unit. These are genuinely part of the exception hierarchy itself — ExceptionGroup inherits from Exception, and BaseExceptionGroup inherits from BaseException — which means they can be caught with a regular except clause like any other exception, in addition to being specifically recognized by the newer except* syntax, which matches sub-groups within an exception group based on the types of exceptions they actually contain.

try:
    raise ExceptionGroup("multiple problems", [ValueError("bad value"), TypeError("bad type")])
except Exception as e:   # a plain except Exception still catches the whole group
    print(type(e))   # <class 'ExceptionGroup'>

Their placement in the hierarchy — ExceptionGroup under Exception, BaseExceptionGroup under BaseException — is a deliberate design choice, keeping them fully consistent with the rest of Python's exception-handling model, rather than functioning as some entirely separate, special-cased mechanism.

The practical takeaway

Understanding this hierarchy isn't an academic exercise — it directly shapes how broadly or narrowly a given except block actually catches errors. Knowing that FileNotFoundError is a kind of OSError, that IndexError and KeyError both descend from LookupError, and that Exception deliberately excludes KeyboardInterrupt and SystemExit, is what lets you write except clauses that catch precisely what you intend — no broader, and no narrower — rather than guessing at exception relationships or over-catching out of uncertainty about how specific and general error types actually relate to one another.

PREVIOUSNEXT LESSON