Show the Python exception hierarchy and how catching a base class catches its subclasses.
Every exception in Python descends from BaseException, and almost everything you will catch descends from Exception.
print(issubclass(ValueError, Exception)) # True
print(issubclass(KeyError, LookupError)) # True
print(issubclass(LookupError, Exception)) # True
print(issubclass(KeyboardInterrupt, Exception)) # False
# Catching the base class catches the subclass
try:
raise KeyError("missing")
except LookupError as err:
print("LookupError caught:", type(err).__name__)The LookupError clause catches a KeyError, because KeyError inherits from LookupError. That is the whole mechanism: an except clause names a class, and it catches that class and everything below it in the tree.
Useful subtrees to know. Under Exception sit ArithmeticError, which covers ZeroDivisionError, OverflowError and FloatingPointError; LookupError, which covers KeyError, IndexError and AttributeError; and OSError, which covers FileNotFoundError, PermissionError, TimeoutError and the rest of the file and network errors. Catching LookupError is often the right level when you want to handle any kind of lookup failure without caring which one it was.
The last line is the one to notice. KeyboardInterrupt is not a subclass of Exception, so an except Exception clause will not catch it — it derives directly from BaseException, along with SystemExit and GeneratorExit. Those three are control-flow signals rather than errors.