Skip to Content
Course content

143: Creating Custom Exception Classes

Click on the "Edit" button in the top corner of the screen to edit your slide content.

I've spent a lot of time reviewing code for junior developers, and one pattern I see constantly is the "generic exception" habit. When something goes wrong in their logic—say, a user tries to withdraw more money than they have in a digital wallet—the instinct is to reach for a built-in exception like ValueError or RuntimeError. It feels correct because, technically, the value provided was "wrong."

def withdraw(balance, amount):
    if amount > balance:
        raise ValueError("Insufficient funds for this transaction")
    return balance - amount

The problem with catching everything

On the surface, the code above works. But here is where it breaks down in a real production system: you lose the ability to be surgical with your error handling. If you wrap a call to withdraw() in a try/except ValueError block, you aren't just catching "insufficient funds" errors. You're catching every single ValueError that might happen inside that block.

Imagine your withdraw function later grows to include a line that converts a string input to an integer using int(). If that conversion fails, Python raises a ValueError. Now, your exception handler—which was meant to tell the user "You're broke"—will accidentally catch a system crash caused by a bad input type and tell the user they don't have enough money. That's a debugging nightmare because you've effectively masked a bug with a user-facing error message.

Defining your own domain language

The better way is to create a custom exception class. In Python, this is incredibly lightweight. You simply create a new class that inherits from Exception (or a more specific built-in if it fits). I like to think of this as creating a "domain language" for my app. Instead of talking in generic Python terms, the code starts talking in business terms.

class InsufficientFundsError(Exception):
    """Raised when a wallet balance is too low for a withdrawal."""
    pass

def withdraw(balance, amount):
    if amount > balance:
        raise InsufficientFundsError(f"Attempted to withdraw {amount} with balance of {balance}")
    return balance - amount

Now, your try/except block can be precise. You can catch InsufficientFundsError to trigger a "Top up your account" UI prompt, while letting a ValueError or TypeError bubble up to your logging system so you know exactly where your code is failing. I've found that this distinction saves hours of head-scratching during on-call rotations.

Avoiding the "Exception Explosion"

Now, a word of caution. It's tempting to create a new class for every single possible error—WrongPasswordError, PasswordTooShortError, PasswordExpiredError. Don't do that. You'll end up with a bloated hierarchy that's just as confusing as the generic approach.

The sweet spot is to create a base exception for your module and then branch off only when the recovery action is different. If the user handles a "too short" password and an "expired" password the exact same way (by being told to "Enter a new password"), one InvalidPasswordError is enough. Only split them into separate classes if you need to execute different logic based on which one was raised.




📋 Practical Task

Implementing a Library Fine Enforcement System

You are building a backend for a local library. The library has a strict rule: if a user has accumulated more than $10.00 in unpaid fines, they are blocked from checking out any new books.

Your task is to:

  • Create a custom exception class named FineLimitExceededError.
  • Write a function checkout_book(user_name, current_fines) that raises this custom exception if current_fines is greater than 10.
  • Implement a try/except block that calls checkout_book. If the FineLimitExceededError is caught, print a message telling the user to visit the front desk to pay their fines.
  • Ensure that any other unexpected errors (like passing a string instead of a number for fines) are not caught by your custom exception handler, allowing them to raise normally.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.