← Python Code Python's Hidden Traps
Browse Python Concepts

Custom Exceptions — When and How to Define Them

Mental Model

Think of custom exceptions as a structured error taxonomy, like a library's catalog system. Instead of rummaging through books (string messages) to guess their genre, you use specific categories and subcategories (exception types) to instantly identify and handle issues precisely.

Rule: When designing library interfaces, always build a structured exception hierarchy inheriting from Exception instead of relying on string matching.

The Setup

You are designing a Stripe payment integration library. If a payment fails, your library needs to raise exceptions. You want to distinguish between validation errors, network failures, and insufficient funds, so that users of your library can respond programmatically.

What Does This Print?

Broken code
Python
class PaymentError(Exception):
    pass

def charge_card(amount, card_valid, funds_available):
    if not card_valid:
        raise PaymentError("Invalid card details")
    if not funds_available:
        raise PaymentError("Insufficient funds")

try:
    charge_card(100, card_valid=True, funds_available=False)
except PaymentError as e:
    # Consumers cannot easily differentiate reasons without string matching
    if "Insufficient" in str(e):
        print("Prompt user to add funds")
Predict why string parsing on exceptions is a dangerous anti-pattern in production environments.

The Output

What actually happens
Prompt user to add funds

While string matching works, it introduces fragile dependencies. If a developer refactors the error message (e.g., changing 'Insufficient funds' to 'No balance'), the calling code will silently fail to handle the specific condition, likely crashing the application.

Why Python Does This

Python allows exceptions to be caught based on class inheritance. By subclassing your custom errors from a domain-specific base class, you create structured error trees. This leverages Python's method resolution order and exception-matching mechanisms, allowing developers to catch specific leaf node exceptions or fallback to general parent exceptions.

The Fix

Corrected pattern
Python
class PaymentError(Exception): """Base exception for all payment failures."""
class InvalidCardError(PaymentError): """Raised when card validation fails."""
class InsufficientFundsError(PaymentError): """Raised when account balance is low."""

def charge_card(amount, card_valid, funds_available):
    if not card_valid:
        raise InvalidCardError("Card formatting error")
    if not funds_available:
        raise InsufficientFundsError("Balance too low")

Creating a custom exception hierarchy allows callers to catch specific, programmatic error types rather than parsing error strings. This provides a robust, future-proof mechanism for handling different error conditions, making the API clearer and less prone to breakage from internal message refactoring.

How This Fails in Real Systems

An automated trading system parsed brokers' API errors using string-matching. When the broker updated their API response message from 'No Liquid Capital' to 'Insufficient Collateral', the system's pattern matcher failed, bypassed safety guards, and kept placing invalid orders.

Key Takeaway

When designing library interfaces, always build a structured exception hierarchy inheriting from Exception instead of relying on string matching.
Common mistake: Developers rely on string matching error messages to differentiate between exception types, creating brittle code that breaks with minor message changes.