Exceptions

This section documents the current Python release line as published at the official Python documentation, which is the reference these pages are written and verified against. No specific patch version is pinned.

This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production.

This section’s bibliography lists the reference material consulted while preparing these pages.

Python signals errors by raising exceptions rather than returning error codes, and handles them with try/except. The full model is covered in the tutorial’s Errors and Exceptions chapter and the built-in exceptions reference.

try / except / else / finally

A try block runs its body; if an exception is raised, the first matching except clause runs instead. else runs only when the body raised nothing; finally always runs, whether or not an exception occurred:

def read_first_line(path):
    try:
        f = open(path)
    except FileNotFoundError:
        print(f"no such file: {path}")
        return None
    else:
        try:
            return f.readline()
        finally:
            f.close()   # always runs: cleans up even if readline() raises

Catch Specific Types, Not Bare except:

Name the exception type(s) you expect. A bare except: (or except Exception: used carelessly) also swallows programming errors — typos, KeyboardInterrupt, SystemExit — that should propagate instead of being silently hidden:

def to_int(text):
    try:
        return int(text)
    except ValueError:               # only the error int() actually raises
        return None

def risky():
    try:
        return 1 / 0
    except:                          # AVOID: catches everything, including Ctrl-C
        return None

A single except clause can name several types as a tuple, and multiple except clauses run in order, the first match wins:

def parse(value):
    try:
        return int(value)
    except (TypeError, ValueError) as exc:
        print(f"bad input: {exc}")
        return 0

The full statement grammar is documented under Handling Exceptions.

raise, Re-Raising, and Exception Chaining

raise with no argument, inside an except block, re-raises the exception currently being handled — useful for logging before letting it propagate:

def load_config(path):
    try:
        return int(open(path).read())
    except ValueError:
        print(f"malformed config at {path}")
        raise   # re-raise the same exception, unchanged

raise X from Y

Raising a new exception from inside an except block automatically chains it to the original as _context_, shown as "During handling of the above exception, another exception occurred". Use raise X from Y to make that link explicit — or raise X from None to suppress it when the original is just noise:

def get_user(user_id, db):
    try:
        return db[user_id]
    except KeyError as exc:
        raise LookupError(f"no user with id {user_id}") from exc   # explicit __cause__

def parse_strict(text):
    try:
        return int(text)
    except ValueError:
        raise ValueError(f"not a number: {text!r}") from None      # hide the low-level cause

Chaining is documented in the same Errors and Exceptions chapter, under "Exception Chaining".

Custom Exception Classes

Subclass Exception (never BaseException directly) to define errors specific to your code. A small hierarchy lets callers catch broadly or narrowly:

class AppError(Exception):
    """Base class for all errors raised by this application."""

class ValidationError(AppError):
    """Input failed validation."""
    def __init__(self, field, reason):
        super().__init__(f"{field}: {reason}")
        self.field = field
        self.reason = reason

class NotFoundError(AppError):
    """A requested resource does not exist."""

def get_item(items, key):
    if key not in items:
        raise NotFoundError(f"no item {key!r}")
    return items[key]

try:
    get_item({}, "widget")
except AppError as exc:       # catches ValidationError, NotFoundError, and any future subclass
    print(f"request failed: {exc}")

Custom exceptions are covered alongside the built-in hierarchy at the exceptions reference and in the tutorial’s User-defined Exceptions section.

Exception Groups (3.11+) and assert

Exception Groups and except*

An ExceptionGroup bundles several unrelated exceptions raised together — typically from concurrent or batch operations where more than one failure can happen independently. except* matches groups by the type of the exceptions inside them, and can run several handlers against the same group:

def validate_all(records):
    errors = []
    for i, record in enumerate(records):
        if not record.get("name"):
            errors.append(ValueError(f"record {i}: missing name"))
        if record.get("age", 0) < 0:
            errors.append(TypeError(f"record {i}: negative age"))
    if errors:
        raise ExceptionGroup("validation failed", errors)

try:
    validate_all([{"age": -1}, {"name": ""}])
except* ValueError as eg:
    print(f"value errors: {[str(e) for e in eg.exceptions]}")
except* TypeError as eg:
    print(f"type errors: {[str(e) for e in eg.exceptions]}")

Each except* clause only "peels off" the matching sub-exceptions; any left unmatched propagate in a new group. Full semantics are in Raising and Handling Multiple Unrelated Exceptions.

assert

assert raises AssertionError if its condition is falsy — intended for catching programmer errors and invariants during development, not for validating user input, since assertions are stripped when Python runs with -O:

def half(n):
    assert n % 2 == 0, f"{n} is not even"
    return n // 2

half(4)    # 2
half(5)    # AssertionError: 5 is not even

assert is documented under the assert statement in the language reference.

Control Flow Through try/except/else/finally

else and finally are easy to place wrong. else runs only on the no-exception path; finally runs on every path, including one that exits via return, break, or an unhandled exception:

flowchart TD A[Enter try block] --> B{Exception raised?} B -->|No| C[else block runs] B -->|Yes| D{Matching except clause?} D -->|Yes| E[except block runs] D -->|No| F[exception propagates unhandled] C --> G[finally block runs] E --> G F --> G G --> H[finally done: re-raise if unhandled, else continue]

The full execution order — including what happens when finally itself contains a return — is specified in the Errors and Exceptions chapter and the built-in exceptions reference.

See Also