Skip to content
  • Exceptions: try, except, raise · Python

Exceptions: try, except, raise

LearnViaNotes @learnvianotes· 3 min read

Errors are objects that propagate up the call stack until something handles them. try / except catches them, raise creates them, and finally (or with) guarantees cleanup.

How an exception travels

  1. Raised

    Something fails (int('x')) or your code runs raise SomeError(...).

  2. Unwinds

    Python abandons the current function and looks for a matching except in each caller, innermost first.

  3. Handled — or not

    The first matching except runs and execution continues after its try statement. With no match, the traceback is printed and the interpreter exits with status 1.

Python
def level3():
    return int("not a number")      # raises ValueError here

def level2():
    return level3()                  # no handler → passes through

def level1():
    try:
        return level2()
    except ValueError:
        return "handled in level1"

print(level1())                      # handled in level1

try / except / else / finally

Python
def parse(value):
    try:
        n = int(value)
    except ValueError as e:
        return f"bad input: {e}"
    else:
        return n * 2            # runs only if no exception
    finally:
        pass                    # always runs: close files, release locks

print(parse("21"))              # 42
print(parse("x"))               # bad input: invalid literal for int() with base 10: 'x'
BlockRuns when
tryAlways — the code that might fail
except EAn exception of type E (or a subclass) was raised
elseThe try block finished without an exception
finallyAlways, even after return, break or an unhandled error

Raising & custom exceptions

Python
class InsufficientFunds(Exception):
    def __init__(self, needed):
        super().__init__(f"need {needed} more")
        self.needed = needed

def withdraw(balance, amount):
    if amount > balance:
        raise InsufficientFunds(amount - balance)
    return balance - amount

try:
    withdraw(100, 150)
except InsufficientFunds as e:
    print(e, e.needed)          # need 50 more 50

try:
    try:
        {}["k"]
    except KeyError as e:
        raise LookupError("config missing") from e    # keep the original cause
except LookupError as e:
    print(repr(e.__cause__))    # KeyError('k')

Exception hierarchy (the useful part)

ExceptionTypical cause
ValueErrorRight type, wrong value: int('x')
TypeErrorWrong type: 'a' + 1
KeyError / IndexErrorMissing dict key / list index out of range (both are LookupErrors)
AttributeErrorObject has no such attribute
NameErrorName not defined (typo, or used before assignment)
ZeroDivisionErrorDivision by zero (an ArithmeticError)
FileNotFoundErrorMissing file (an OSError)
StopIterationIterator is exhausted
RecursionErrorRecursion deeper than sys.getrecursionlimit()

The full tree, BaseException vs Exception, exception groups and chaining are in Exception Hierarchy & Advanced Handling.

Guaranteed cleanup: finally and with

Python
from contextlib import suppress

events = []
def risky():
    try:
        events.append("open")
        raise RuntimeError("boom")
    finally:
        events.append("close")          # runs even though the error escapes

try:
    risky()
except RuntimeError:
    pass
print(events)                           # ['open', 'close']

with suppress(FileNotFoundError):       # ignore one specific, expected error
    open("definitely-missing.txt")
print("still running")                  # still running

What is the difference between else and finally in a try statement?

Free for students and teachers

Write notes like this one

Cards for every idea, quizzes and flashcards, read aloud — on the web and your phone.

Something wrong with this note? Open it to report