3.2 The Built-in Exception Hierarchy
Key Takeaways
- All built-in exceptions form an inheritance hierarchy rooted at BaseException, with standard application errors inheriting from Exception.
- SystemExit, KeyboardInterrupt, and GeneratorExit inherit directly from BaseException so that catching Exception will not block normal exit signals or Ctrl+C.
- ArithmeticError is the base class for ZeroDivisionError, OverflowError, and FloatingPointError.
- LookupError is the parent class for both IndexError (sequence subscripting) and KeyError (dictionary key lookups).
- Exception matching operates polymorphically: catching a parent class in an except block intercepts all current and future derived subclasses.
The Built-in Exception Hierarchy
In Python, all exceptions are regular Python objects instantiated from classes organized in a strictly defined inheritance tree. When an error occurs, Python uses standard object-oriented polymorphism to match the raised exception instance against the classes specified in except clauses. Mastering this hierarchy is vital for writing precise exception handlers and answering classification questions on the PCAP exam.
The Root Architecture: BaseException vs Exception
At the root of the entire exception system sits BaseException. However, Python intentionally splits its root hierarchy into two distinct functional categories.
BaseException
├── SystemExit
├── KeyboardInterrupt
├── GeneratorExit
└── Exception
├── ArithmeticError
│ ├── FloatingPointError
│ ├── OverflowError
│ └── ZeroDivisionError
├── LookupError
│ ├── IndexError
│ └── KeyError
├── TypeError
├── ValueError
├── AttributeError
├── NameError
│ └── UnboundLocalError
├── ImportError
│ └── ModuleNotFoundError
├── OSError
│ ├── FileNotFoundError
│ ├── PermissionError
│ ├── IsADirectoryError
│ └── FileExistsError
└── SyntaxError
└── IndentationError
└── TabError
Why BaseException Exists Separately from Exception
Python distinguishes between system-exiting events and application-level errors:
SystemExit: Raised bysys.exit()when a program requests termination.KeyboardInterrupt: Raised when the user sends an interrupt signal (such as typingCtrl+C).GeneratorExit: Raised inside a generator when itsclose()method is called.Exception: The base class for all non-system-exiting, user-handleable application exceptions.
[!IMPORTANT] The Broad Catch Rule: User code should almost always catch
except Exception:, neverexcept BaseException:. If you catchBaseException(or use a bareexcept:), your program will trapKeyboardInterruptandSystemExit, making it impossible for users to terminate the script viaCtrl+Cor standard system signals.
# BAD PRACTICE: Traps Ctrl+C and sys.exit()
try:
run_server()
except BaseException: # Or bare 'except:'
print("Ignored exit signal!")
# GOOD PRACTICE: Catches application errors, allows clean termination
try:
run_server()
except Exception as err:
logger.error(f"Application error: {err}")
Major Built-in Exception Families
Let us analyze the major built-in exception families tested on the PCAP exam.
1. ArithmeticError Family
ArithmeticError is the common base class for mathematical calculation errors:
ZeroDivisionError: Raised when the second argument of a division (/,//) or modulo (%) operation is zero across integer, float, or Decimal types.OverflowError: Raised when an arithmetic operation produces a numeric result too large to be represented. Note: In Python 3, integers have arbitrary precision and will not raiseOverflowErrorduring standard addition or exponentiation; however, floating-point numbers have fixed 64-bit IEEE 754 precision and raiseOverflowErroron operations likemath.exp(1000)or2.0 ** 2000.FloatingPointError: Raised when a floating-point operation fails and hardware floating-point exception traps are configured.
import math
try:
val = math.exp(1000) # Float calculation exceeds IEEE 754 limit
except ArithmeticError as e:
print(f"Caught arithmetic error: {type(e).__name__}")
# Output: Caught arithmetic error: OverflowError
2. LookupError Family
LookupError is the base class for errors that occur when a key or index used to access a collection is invalid:
IndexError: Raised when a sequence subscript is out of range (e.g. accessing index 5 of a 3-element list or string).KeyError: Raised when a mapping (dictionary) key is not found in the set of existing keys.
def safe_lookup(collection, key_or_idx):
try:
return collection[key_or_idx]
except LookupError as err:
return f"Lookup failed: {type(err).__name__}"
print(safe_lookup([10, 20, 30], 99)) # Output: Lookup failed: IndexError
print(safe_lookup({'a': 1}, 'z')) # Output: Lookup failed: KeyError
3. TypeError vs ValueError
Distinguishing between TypeError and ValueError is one of the most frequently tested concepts:
| Exception | Formal Definition | Real-World Trigger Examples |
|---|---|---|
TypeError | An operation or function is applied to an object of inappropriate type. | 'hello' + 5<br>len(1234)<br>[1, 2][1.5] |
ValueError | A function receives an argument that has the correct data type but an inappropriate value. | int('abc') (str is correct type, but 'abc' is not digits)<br>math.sqrt(-10) (int is correct type, but negative is invalid) |
4. NameError and UnboundLocalError
NameError: Raised when a local or global name is not found in the active namespaces.UnboundLocalError: A direct subclass ofNameError. Raised when a reference is made to a local variable in a function, but no value has been assigned to that variable yet.
x = 10
def modify():
print(x) # UnboundLocalError: local variable 'x' referenced before assignment
x = 20
Because modify contains the assignment x = 20, Python marks x as local to the entire function scope. The print(x) statement tries to read the local x before the assignment executes.
5. AttributeError
Raised when an attribute reference or assignment fails on an object (e.g. calling a nonexistent method 'hello'.push(10) or accessing math.nonexistent_constant).
6. ImportError and ModuleNotFoundError
ModuleNotFoundError: A subclass ofImportError, raised when animportstatement cannot locate the specified module file.ImportError: Raised when the module file is located, but a specific name cannot be imported from it (e.g.from math import nonexistent_func).
7. OSError and File System Exceptions
OSError represents operating-system-level errors. In modern Python 3, many older exceptions (IOError, EnvironmentError) were merged under OSError, which has concrete subclasses:
FileNotFoundError: Attempting to read a nonexistent file path.PermissionError: Insufficient filesystem access rights.IsADirectoryError: Attempting file operations on a folder.FileExistsError: Attempting to create an already existing file with exclusive mode ('x').
8. Compile-time vs Runtime Exceptions: SyntaxError
SyntaxError occurs when the Python parser encounters invalid code grammar during the compilation/parsing phase. Subclasses include IndentationError and TabError.
[!NOTE] A
SyntaxErrorwithin the same file cannot be caught by atryblock in that same file because Python fails to parse the script before execution begins. However, aSyntaxErrorcan be caught at runtime if it is generated dynamically viaeval(),exec(), orimportlib.import_module().
Polymorphic Catch Mechanics
Because exception matching is implemented via issubclass(), an except clause specifying a parent class intercepts instances of that parent class as well as all descendant classes.
# Demonstration of Polymorphic Matching
print(issubclass(ZeroDivisionError, ArithmeticError)) # True
print(issubclass(ZeroDivisionError, Exception)) # True
print(issubclass(IndexError, LookupError)) # True
print(issubclass(KeyError, LookupError)) # True
print(issubclass(UnboundLocalError, NameError)) # True
Which of the following exceptions does NOT inherit from the Exception class?
Which exception is the common superclass for both IndexError and KeyError?
What is the primary difference between a TypeError and a ValueError?
What will be printed when the following code is executed? def test_scope(): try: print(issubclass(UnboundLocalError, NameError)) except Exception: print(False) test_scope()