Free Handbook · Runs in your browser

Exceptions

What an exception is and how it travels up the call stack, try / except / else / finally with the rules for catching well, raising your own and defining custom exception classes, assert, and how to read any traceback from the bottom up.

0 / 108 lessons🔥 0 day streak
ShareXLinkedIn

Module 07 · what you'll be able to do

  • Explain what happens when an exception is raised and nobody catches it
  • Write try / except that catches exactly the errors you expect, and nothing else
  • Use else and finally correctly, and the with statement as the finally you do not have to write
  • Raise exceptions with useful messages and define your own exception hierarchy
  • Read a traceback bottom-up and locate the line that matters in under ten seconds
01

What an exception is

When something goes wrong at runtime — a missing key, a bad conversion, a file that is not there — Python creates an exception object and raises it. Raising stops the current line and unwinds: out of the function, out of its caller, and so on up the stack, until some except catches it. If nothing does, the program prints the traceback and exits. That unwinding is the point: the error is handled at whatever level knows what to do about it, not where it happened.

VisualizeAn exception unwinding through three callsStep 1 / 6
def parse(text):
return int(text)
def load(rows):
return [parse(r) for r in rows]
try:
load(["1", "2", "x"])
except ValueError as e:
print("bad row:", e)
Line 8

Enter the try block and call load.

Variables now
stack[module]
All 6 steps as a table
StepLineWhat happenedVariables now
18Enter the try block and call load.stack = [module]
25The comprehension calls parse("1") → 1, parse("2") → 2, then parse("x").stack = [module, load, parse]
32int("x") cannot work. Python creates ValueError("invalid literal for int() with base 10: 'x'") and raises it. parse has no try, so its frame is abandoned.stack = [module, load]
45load has no try either. Its frame is abandoned too — the half-built list is discarded.stack = [module]
59Back in the module, the raise happened inside a try whose except ValueError matches. The exception is caught and bound to e.
610Handle it. Execution continues after the try/except as normal.
pythonEdit it. ⌘/Ctrl + Enter runs.
You should see
bad row: invalid literal for int() with base 10: 'x'
type: ValueError
the program continues
Your turn
Remove the try/except and run again. Read the traceback: it lists module → load → parse, innermost last, then the error.
02

try / except / else / finally

Four clauses, and each has one job. try: the code that might fail. except: what to do if it does — one per error type. else: runs only if nothing was raised — keeps the "happy path" out of the try so you do not accidentally catch errors from it. finally: runs no matter what — cleanup.

pythonEdit it. ⌘/Ctrl + Enter runs.
You should see
  ok, no error raised
  finally: always runs (even after return)
5.0
  cannot divide by zero
  finally: always runs (even after return)
None
  bad input: unsupported operand type(s) for /: 'str' and 'int'
  finally: always runs (even after return)
None
42
skipped '4.2': ValueError
skipped '': ValueError
skipped None: TypeError

Catching badly

  • except: — catches everything, including Ctrl+C and typos in your own code
  • except Exception: pass — the bug is now invisible
  • A try block wrapping 40 lines — which one raised?
  • Catching an error you cannot do anything about

Catching well

  • except ValueError: — the specific thing you expected
  • Log it, or re-raise with raise, or return a sentinel — but never swallow it
  • A try block of one or two lines around the risky call
  • Let it propagate to the caller who can decide (often: crash loudly)
with is a finally you do not have to write
with open(...) as f: closes the file whether the block succeeds or raises. Locks, database transactions and temporary directories all support with for the same reason. Any time you find yourself writing finally: x.close(), look for a context manager.
Quick check

Where should the code that uses a successfully-parsed value go?

03

Raising, and custom exceptions

Your own code should raise too. When a function is called with something it cannot handle, raise ValueError("why") is far better than returning None or -1 and hoping the caller checks. Use the built-in types when they fit (ValueError for bad values, TypeError for wrong types, KeyError, FileNotFoundError), and define your own when callers need to distinguish your failures from everyone else's.

pythonEdit it. ⌘/Ctrl + Enter runs.
You should see
new balance: 70
declined — need 30, have 10 | short by 20
bug in the caller: amount must be positive
could not parse amount | caused by: ValueError("invalid literal for int() with base 10: 'abc'")
Your turn
Add a CardDeclined raise for amounts over 1000 and handle it separately.
  • Subclass Exception, never BaseException (that is for system exits and Ctrl+C).
  • One base class per library or module, so callers can except PaymentError and get everything of yours.
  • Order except clauses from specific to general — a base class first would shadow its subclasses.
  • A bare raise inside an except re-raises the current exception unchanged; raise X from e keeps the original as __cause__ so the traceback shows both.
04

assert

assert condition, "message" raises AssertionError if the condition is false. It is for things that cannot be false unless your code has a bug — invariants, sanity checks in tests — not for validating user input. Reason: running Python with -O strips every assert, so any check that matters in production must be a real if … raise.

pythonEdit it. ⌘/Ctrl + Enter runs.
You should see
4.0
AssertionError: average of nothing is undefined
tests passed
Quick check

Which check belongs in an assert?

05

Reading a traceback (bottom-up)

A traceback is read from the bottom. The last line is the error type and message — that tells you what. The lines above it are the call stack, outermost first, innermost last: the last File … line … entry is where. The frames above that are how you got there. Once a traceback stops looking like a wall of text, debugging becomes a ten-second task.

texta real traceback, annotated
Traceback (most recent call last):                       ← 4. "most recent call LAST": read upward from the bottom
  File "app.py", line 31, in <module>                     ← 3. the entry point: main() was called here
    main()
  File "app.py", line 22, in main                          ← 2. main called process_orders
    total = process_orders(orders)
  File "app.py", line 12, in process_orders                ← 1. WHERE: the line that raised, in YOUR code
    price = row["price"]
KeyError: 'price'                                          ← 0. WHAT: start here
  1. 1
    Read the last line

    Type + message. KeyError: 'price' — a dict had no key called price.

  2. 2
    Find the last frame in your code

    Library frames (paths under site-packages) are usually not the bug; the last frame in a file you wrote is. Here: app.py line 12.

  3. 3
    Look at that line and its variables

    row["price"]. So row had no "price". Print row (or row.keys()) just before that line and run again.

  4. 4
    Walk up if needed

    If line 12 looks right, the wrong data came from above: process_orders(orders) on line 22 — where did orders come from?

pythonEdit it. ⌘/Ctrl + Enter runs.
You should see
Traceback (most recent call last):
  File "your code", line 10, in <module>
  File "your code", line 7, in outer
  File "your code", line 4, in inner
KeyError: 'price'
still running

Run from a real file, each frame also shows its source line (as in the annotated example above); the browser runs code from memory, so only the file, line and function appear. traceback.print_exc() is how a server logs an error and keeps serving; logging.exception("…") does the same with a timestamp (Module 11).

Practice this — graded problems in your browser

Finish the Python handbook, then get hired

Sit the exam for your certificate, run your resume through the ATS checker, and see the jobs that ask for exactly this.

Check my resume
Found this course useful? Share it.
ShareXLinkedIn

Comments

0

Join the conversation. Sign in to leave a comment — we'd love to hear your thoughts.