Exception Handling
🚨 Exception Handling
KeyError from a missing dict key (Course 1, Chapter 7), a TypeError from mutating a tuple. This chapter covers handling them deliberately: try/except/else/finally, raising your own exceptions, and defining custom exception types.
🛡️ try/except Basics
Code inside try runs normally until an exception occurs; the matching except block then runs instead of letting the program crash. Multiple except blocks let you handle different failure types differently.
A bare except: with no exception type swallows everything — including typos that raise NameError, programming bugs that raise TypeError, and even KeyboardInterrupt (Ctrl+C) and SystemExit, making the program impossible to stop cleanly. Always catch specific exception types (except ValueError:), or at minimum except Exception:, which excludes those system-level signals.
🔀 else and finally
This is the same else-means-"no exception fired" idea as the loop else clause from Course 1, Chapter 4 — else here runs only when the try block completed with no exception. finally always runs, whether an exception occurred or not — the standard place for cleanup code (closing a file or network connection) that must happen either way.
📢 Raising Exceptions
raise triggers an exception deliberately — as e captures the exception object so you can inspect its message with str(e) or, as shown, directly in an f-string.
🏷️ Custom Exceptions
A custom exception is just a class inheriting from Exception (usually with nothing else needed — pass is enough) — reusing the exact class/inheritance mechanics from Chapter 2. Naming it specifically (InsufficientFundsError rather than a generic ValueError) makes both the raising code and the catching code more self-documenting.
Error Handling: Go vs Kotlin vs Python
| Language | Approach |
|---|---|
| Go | No exceptions at all — functions return an explicit (value, error) pair, and the caller checks if err != nil after every call. Failure is just a normal return value. |
| Kotlin | Exceptions, like Java — but all unchecked (no throws declarations the compiler enforces), so a function's signature alone never tells you what it might throw. |
| Python | Exceptions, always unchecked, propagating up the call stack until caught by a try/except or crashing the program. |
Go's explicit-error-return style is a genuinely different philosophy from Python's — every Go function that can fail says so directly in its return type, where Python (and Kotlin) require reading the implementation or documentation to know what might be raised.
try / except
Catches specific exception types; avoid a bare except:.
else / finally
else runs only on success; finally always runs, for cleanup.
raise
Triggers an exception deliberately, optionally with a message.
Custom exceptions
A class inheriting from Exception — self-documenting failure types.
💻 Coding Challenges
Challenge 1: Safe Division Function
Write a function safe_divide(a, b) that returns a / b, but catches ZeroDivisionError and returns None instead of crashing when b is 0. Call it with (10, 2) and (10, 0), printing both results.
Goal: Practice basic try/except around a real failure case.
Challenge 2: Validated Age Input
Write a function set_age(age) that raises a ValueError with the message "Age cannot be negative" if age < 0, and otherwise returns the age unchanged. Call it with a valid age inside a try/except, then with -5, printing the caught error message.
Goal: Practice raise-ing a built-in exception type with a custom message, and catching it with as e.
Challenge 3: Custom Exception for Stock Levels
Define a custom exception OutOfStockError(Exception). Write a function purchase(stock, quantity) that raises it (with a descriptive message) if quantity > stock, and otherwise returns stock - quantity. Demonstrate it raising and being caught.
Goal: Practice defining and using a custom exception end to end.
🎯 What's Next
Next chapter: File I/O — reading/writing files, context managers (the with statement), and pathlib.