Python ships with plenty of built-in exceptions — ValueError, TypeError, KeyError, and more — but they can't describe every failure specific to your own application. When "insufficient balance" or "invalid user age" happens, raising a generic ValueError doesn't tell the calling code (or the next developer reading it) what actually went wrong in domain terms. That's what custom exceptions are for.
Defining a Custom Exception
A custom exception is just a class that inherits from Exception (or one of its built-in subclasses). Most of the time it needs no extra code at all — inheriting is enough to make it a fully working exception type.
class InsufficientBalanceError(Exception):
pass
def withdraw(balance, amount):
if amount > balance:
raise InsufficientBalanceError(
f"Cannot withdraw {amount}, balance is only {balance}"
)
return balance - amount
try:
withdraw(100, 250)
except InsufficientBalanceError as e:
print(f"Transaction failed: {e}")
Output:
Transaction failed: Cannot withdraw 250, balance is only 100
Adding Extra Data to a Custom Exception
You can override __init__ to attach extra information to the exception, which calling code can then inspect — not just a message string.
class InsufficientBalanceError(Exception):
def __init__(self, balance, amount):
self.balance = balance
self.amount = amount
super().__init__(f"Cannot withdraw {amount}, balance is only {balance}")
try:
raise InsufficientBalanceError(balance=100, amount=250)
except InsufficientBalanceError as e:
print(e)
print(f"Short by: {e.amount - e.balance}")
Output:
Cannot withdraw 250, balance is only 100
Short by: 150
When a Custom Exception Is Worth Creating
Create a custom exception when the failure represents a meaningful, named concept in your application's domain — something calling code might reasonably want to catch specifically and handle differently from other errors. If you're just going to catch it and print the same generic message as a built-in ValueError would give you, a custom class is probably unnecessary overhead. A good rule of thumb: if you find yourself checking an error message string to figure out what went wrong, that's a sign you should have raised a distinct exception type instead.
Custom exceptions are commonly organized into a small hierarchy — e.g. a base AppError that more specific exceptions inherit from — which pairs naturally with what you learned about inheritance and try/except exception handling.
