1.3.7 · D5Python Intermediate
Question bank — Exception hierarchy
True or false — justify
Every exception in Python is a subclass of Exception.
False — every exception subclasses BaseException, but
SystemExit, KeyboardInterrupt, and GeneratorExit are direct children of BaseException and do not inherit from Exception.except Exception: will catch a Ctrl+C (KeyboardInterrupt).
False —
KeyboardInterrupt is a sibling of Exception under BaseException, not a child of it, so except Exception lets it pass through.except LookupError: catches both KeyError and IndexError.
True — both are direct children of
LookupError, so a handler for the parent catches either child.except ZeroDivisionError: will catch an OverflowError.
False — they are siblings under
ArithmeticError, not parent/child, so one handler never catches the other. You'd need except ArithmeticError to catch both.If you catch ArithmeticError, a ZeroDivisionError raised inside will be handled.
True —
ZeroDivisionError is-a ArithmeticError (a child), and a handler for a parent always catches its children.Placing except Exception before except KeyError is a syntax error.
False — it is valid syntax but a logic bug: the
KeyError branch becomes dead code because Exception matches first (KeyError is-a Exception).FileNotFoundError and PermissionError are unrelated classes.
False — both are children of OSError, so a single
except OSError catches either as a safety net.except BaseException: is always safer than except Exception: because it catches more.
False — catching more is a danger, not a feature: it swallows
SystemExit and KeyboardInterrupt, so your program can't be stopped normally.A bare except: (no class) behaves like except BaseException:.
True — a bare except matches everything including system-exit events, which is why it's discouraged in the same way
except BaseException is.StopIteration is a child of Exception, so except Exception catches it.
True —
StopIteration sits directly under Exception, and normally you'd never blanket-catch it because it's the signal that drives for loops.Spot the error
try:
x = d[key]
except Exception:
print("general")
except KeyError:
print("key missing")What's wrong?
The
KeyError branch is unreachable. Since KeyError is-a Exception, the general handler above it always matches first — order must go specific → general.try:
risky()
except BaseException:
print("caught it")What's wrong?
This swallows
KeyboardInterrupt and SystemExit, so Ctrl+C and sys.exit() are silently ignored. Use except Exception for a normal catch-all.try:
total = 1e308 / 1e-10
except ZeroDivisionError:
print("zero div")Why doesn't this handler fire on the overflow?
The result overflows the float range and raises
OverflowError, which is not a ZeroDivisionError (they're only siblings under ArithmeticError). The exception propagates uncaught.try:
open("missing.txt")
except OSError:
print("os error")
except FileNotFoundError:
print("no file")What's the ordering bug?
FileNotFoundError is a child of OSError, so the OSError line catches it first and the specific FileNotFoundError branch is dead code. Swap them.try:
for x in gen():
use(x)
except Exception:
print("done")Why is catching Exception here subtly dangerous?
Generators signal completion with
StopIteration (a subclass of Exception); accidentally catching it can mask real iteration bugs or hide that a loop ended early. Catch only what you actually expect.except (KeyError, LookupError):
handle()Is listing both in one tuple useful?
No — it's redundant since
KeyError is already a LookupError; the tuple is equivalent to just except LookupError. It signals confusion about the hierarchy.Why questions
Why does Python need BaseException at the very top instead of just Exception?
So there is one common ancestor for everything, while still letting
except Exception catch ordinary errors without accidentally swallowing control-flow events like SystemExit and KeyboardInterrupt.Why must specific handlers come before general ones?
Python checks
except clauses top-to-bottom and stops at the first class match. A parent listed first will always match before its child, making the child's clause dead code.Why prefer except LookupError over writing separate except KeyError and except IndexError?
When you want the same recovery for both, one parent handler is cleaner and covers any future
LookupError subtype too — you exploit shared inheritance instead of duplicating logic.Why is except OSError often placed as a last handler in file code rather than the only one?
It acts as a safety net for the 20+ OS-level failures (disk full, network drop) after the common cases (
FileNotFoundError, PermissionError) have specific recovery paths above it.Why does a logging or cleanup framework sometimes legitimately use except BaseException?
Because it must run even during shutdown or
Ctrl+C — but it always re-raises afterward so it doesn't actually stop the termination it observed. See 3.2.05-Logging and 1.3.09-Context-Managers.Why does the hierarchy give "surgical precision"?
Choosing the right ancestor sets exactly how wide your net is: a leaf class catches one thing, a mid-level parent catches a related family,
Exception catches almost all — you pick the narrowest one that still covers your intent.Why should a custom exception usually subclass Exception and not BaseException?
So that normal
except Exception catch-alls in calling code will catch it as expected; subclassing BaseException would make it dodge every ordinary handler. See 1.3.08-Custom-Exceptions.Edge cases
If no except clause matches a raised exception, what happens?
Python keeps walking up the call stack looking for a handler; if it reaches the top with none, the program crashes and prints a traceback.
Does finally still run if an exception is raised but not caught?
Yes —
finally runs on the way out regardless of whether the exception was handled, which is why it's used for cleanup. See 1.3.06-TryExcept-Finally.What does except Exception as e bind when KeyError('x') is raised?
e binds the actual KeyError instance (not a generic Exception), so type(e) is KeyError and str(e) shows 'x' — you keep the specific object even through a parent handler.Can two sibling exceptions (e.g. IndexError and KeyError) ever be caught by the same single class handler other than a shared ancestor?
No — the only class that catches both is a common ancestor (
LookupError, Exception, or BaseException); siblings have no direct catch relationship between themselves.Is except (): (empty tuple) valid, and what does it catch?
It's valid syntax but catches nothing — an empty tuple of exception types never matches, so the clause is effectively dead. It's a subtle way to write a handler that never fires.
What happens if a raise occurs inside an except block?
The new exception propagates out of the
try statement entirely (unless nested in another try), and Python chains it as the "during handling of the above" context in the traceback.Does catching ArithmeticError protect against an integer 10**1000 / 10**999?
There's nothing to protect — that expression equals
10.0 and raises nothing, because Python promotes large integers correctly and only float results exceeding ~ raise OverflowError.Recall Quick self-test
Which single ancestor catches KeyError, IndexError, but NOT ZeroDivisionError? ::: LookupError — it parents the two lookup errors, while ZeroDivisionError lives under ArithmeticError on a different branch.
What's the one class you should almost never catch, and why? ::: BaseException — it swallows SystemExit/KeyboardInterrupt, breaking your ability to stop the program.