Exception Handling

Python Intermediate — Exception Handling
Python Intermediate
Course 2 · Chapter 3 · Exception Handling

🚨 Exception Handling

You've already seen a few exceptions crash a program by accident — a 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

try: result = 10 / 0 except ZeroDivisionError: print("Can't divide by zero!") # catching multiple, specific exception types: try: value = int(input("Enter a number: ")) except ValueError: print("That wasn't a valid number") except KeyboardInterrupt: print("Input cancelled")

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: Catches Everything — Including Things You Don't Want
try: risky_operation() except: # catches ALL exceptions, even ones you didn't anticipate print("something went wrong")

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

try: value = int("42") except ValueError: print("conversion failed") else: print(f"conversion succeeded: {value}") # runs only if NO exception occurred finally: print("this always runs") # runs no matter what

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

def withdraw(balance, amount): if amount > balance: raise ValueError("Insufficient funds") return balance - amount try: withdraw(100, 150) except ValueError as e: print(f"Error: {e}") # Error: Insufficient funds

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

class InsufficientFundsError(Exception): pass def withdraw(balance, amount): if amount > balance: raise InsufficientFundsError(f"Cannot withdraw {amount}, balance is only {balance}") return balance - amount try: withdraw(100, 150) except InsufficientFundsError as e: print(e) # Cannot withdraw 150, balance is only 100

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

LanguageApproach
GoNo 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.
KotlinExceptions, like Java — but all unchecked (no throws declarations the compiler enforces), so a function's signature alone never tells you what it might throw.
PythonExceptions, 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.

→ Solution

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.

→ Solution

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.

→ Solution

🎯 What's Next

Next chapter: File I/O — reading/writing files, context managers (the with statement), and pathlib.