Custom Exceptions & Defensive Coding Patterns
You'll learn to
- -Define a custom exception by subclassing `Exception`
- -Decide when to raise an exception vs. return `None`/a sentinel value
- -Understand `assert` and how it differs from raising an exception for expected failures
Built-in exceptions like `ValueError` and `KeyError` cover generic cases, but well-designed code often defines its own exception types so that callers can catch exactly the failure they care about, and so the traceback itself documents what went wrong in domain terms, not generic ones.
Defining a Custom Exception
A custom exception is just a class that inherits from `Exception` (or one of its built-in subclasses). Most of the time, the body is nothing but `pass`. The value of a custom exception is its distinct type, which lets callers catch it specifically, not any special behavior inside it.
A caller can now catch `InsufficientFundsError` specifically, without accidentally also swallowing an unrelated `ValueError` from somewhere else in the same `try` block, the whole point of catching narrow, specific exception types from the previous chapter.
Raise vs. Return None vs. Sentinel Values
A recurring design decision: when an operation cannot produce a normal result, should the function raise an exception, or return something like `None` to signal "no result"? A useful rule of thumb: raise when the failure is exceptional and callers should be forced to notice it. Return `None` (or another sentinel) when "not found" or "no result" is a completely normal, expected outcome that most callers will want to just check with an `if`.
This exact decision shows up constantly in Forge Algorithm Lab levels: a hash map or dictionary-like class whose spec says "raise KeyError if the key is not found" is asking for Pattern 1 above, explicitly and deliberately, because that is the behavior a real Python dict has too.
assert: Internal Invariants, Not Expected Failures
The `assert` statement checks a condition and raises `AssertionError` if it is false. It exists for internal invariants, things that should always be true if your own code is correct, not for validating external input or handling conditions you actually expect to occur, like a missing key or bad user input. The key distinction: an exception you `raise` deliberately is part of your function's documented behavior. An `assert` failing means there is a bug in your own logic.
- -A custom exception subclasses `Exception`, usually with just `pass`. The distinct type is the whole point.
- -Raise when a caller must be forced to notice and handle a failure. Return `None` or a sentinel when "no result" is a normal, expected outcome.
- -`assert` is for catching your own bugs (internal invariants) during development, not a substitute for validating untrusted or expected-to-vary input.
- -Python can be run with optimizations that strip out `assert` statements entirely, which is another reason never to rely on them for real input validation.
Interview Signal is part of Pro
See a real weak answer next to a real strong one for this exact topic.
Quiz is part of Pro
Test what you just read with a short quiz, and bank the XP.
Module Assignment is part of Pro
Write real, graded Python for this module - right here in the course.