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.
Last updated: August 2026

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:

  1. SystemExit: Raised by sys.exit() when a program requests termination.
  2. KeyboardInterrupt: Raised when the user sends an interrupt signal (such as typing Ctrl+C).
  3. GeneratorExit: Raised inside a generator when its close() method is called.
  4. 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:, never except BaseException:. If you catch BaseException (or use a bare except:), your program will trap KeyboardInterrupt and SystemExit, making it impossible for users to terminate the script via Ctrl+C or 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 raise OverflowError during standard addition or exponentiation; however, floating-point numbers have fixed 64-bit IEEE 754 precision and raise OverflowError on operations like math.exp(1000) or 2.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:

ExceptionFormal DefinitionReal-World Trigger Examples
TypeErrorAn operation or function is applied to an object of inappropriate type.'hello' + 5<br>len(1234)<br>[1, 2][1.5]
ValueErrorA 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 of NameError. 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 of ImportError, raised when an import statement 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 SyntaxError within the same file cannot be caught by a try block in that same file because Python fails to parse the script before execution begins. However, a SyntaxError can be caught at runtime if it is generated dynamically via eval(), exec(), or importlib.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
Loading diagram...
Built-in Exception Class Inheritance
Test Your Knowledge

Which of the following exceptions does NOT inherit from the Exception class?

A
B
C
D
Test Your Knowledge

Which exception is the common superclass for both IndexError and KeyError?

A
B
C
D
Test Your Knowledge

What is the primary difference between a TypeError and a ValueError?

A
B
C
D
Test Your Knowledge

What will be printed when the following code is executed? def test_scope(): try: print(issubclass(UnboundLocalError, NameError)) except Exception: print(False) test_scope()

A
B
C
D