← All Articles

Python Exceptions Cheat Sheet: The Hierarchy and the Fixes

At a Glance
Traceback ending in an exception nameexcept clause not catching what you expectNeed to pick the right exception to raise

Read the last line of the traceback first: the exception name plus message is the diagnosis; the traceback above it is the crime scene.

The Hierarchy That Explains Catching

Exceptions are classes, and except matches by inheritance. BaseException → Exception → specific types. Everything below inherits from Exception, which is why except Exception catches nearly everything — and why you should not do it casually.

BaseException
 ├── SystemExit          # sys.exit() — don't catch
 ├── KeyboardInterrupt   # Ctrl+C — don't catch
 └── Exception
      ├── ArithmeticError → ZeroDivisionError, OverflowError
      ├── LookupError → IndexError, KeyError
      ├── OSError → FileNotFoundError, PermissionError, FileExistsError
      ├── ValueError, TypeError, AttributeError
      ├── ImportError → ModuleNotFoundError
      ├── RuntimeError → RecursionError
      └── StopIteration, NotImplementedError

The Exceptions You Actually Meet

ExceptionTriggered byFix
TypeErrorWrong type: "a" + 1, calling a non-callableConvert explicitly; check the type before the operation
ValueErrorRight type, bad value: int("abc")Validate before parsing; use try/except around the conversion
KeyErrorMissing dict keyd.get(k, default), or check k in d. KeyError guide
IndexErrorSequence index out of rangeCheck length, use slicing, or iterate directly
AttributeErrorAttribute doesn't exist on the objectCheck for None; verify the attribute name. AttributeError guide
ModuleNotFoundErrorimport of a missing moduleInstall it; check the venv. Guide
FileNotFoundErrorOpening a path that doesn't existCheck the path relative to CWD; use pathlib
PermissionErrorOS denied the operationCheck file ownership/mode, not sudo first. Guide
ZeroDivisionErrorx / 0, x % 0Guard the denominator; or catch and return a sentinel
IndentationErrorMixed tabs/spaces or wrong indentPick one indentation style; re-indent the block. Guide
RecursionErrorRecursion beyond the limitAdd a base case, or convert to iteration
StopIterationnext() on an exhausted iteratorUse a for loop or provide a default to next()

The except Ordering Trap

Python checks except clauses top to bottom and uses the first match — so a broad clause placed first shadows the specific ones below it:

# WRONG: ValueError is unreachable
try:
    x = int(user_input)
except Exception:
    print("generic")     # always wins
except ValueError:
    print("bad number")  # dead code

The same rule makes except (OSError, IOError) redundant (IOError is an alias of OSError). Order: specific first, broad last. Linters flag unreachable except blocks; treat that warning as a bug.

The Full try Shape (Most People Only Use Half)

try:
    data = load(path)
except FileNotFoundError:
    data = default()
else:
    log.info("loaded")   # runs only if NO exception occurred
finally:
    cleanup()            # runs no matter what

else is the underused clause: code that should run only on success belongs there, not inside try — putting it in try makes its own exceptions look like they came from the guarded call, confusing your handlers.

Raising and Custom Exceptions

class RateLimitError(Exception):
    def __init__(self, retry_after):
        super().__init__(f"rate limited; retry in {retry_after}s")
        self.retry_after = retry_after

raise RateLimitError(30)

Two rules that keep exception design healthy: raise ValueError/TypeError for programming errors and let them propagate — do not convert them into silent defaults; use custom exceptions for domain conditions callers must handle (insufficient funds, rate limits). And when re-raising, use raise ... from err to keep the causal chain — bare raise inside except is fine (re-raises the active exception), but raise NewError() without from hides the original traceback.

Related Errors

ModuleNotFoundError · IndentationError · KeyError