Custom Exceptions
Create your own exception types so errors in your code are specific, descriptive, and easy to catch separately from built-in Python errors.
"Built-in exceptions like ValueError are generic. A custom exception like InsufficientFundsError tells you exactly what went wrong, just by reading its name."
— ShurAIWhy Create Custom Exceptions?
Python has many built-in exceptions, but they are generic. When you write a library or application, custom exceptions make errors self-documenting and easy to catch specifically:
raise ValueError("bad") except ValueError: # Was it age? name? # balance? username? # We have no idea.
raise InsufficientFundsError() except InsufficientFundsError: # Unambiguous. # Show "not enough balance" # message to the user.
Creating a Custom Exception
A custom exception is just a class that inherits from Exception. The simplest version needs only one line of body:
class InsufficientFundsError(Exception):
pass # inherits everything from Exception
# Use it exactly like any built-in exception
raise InsufficientFundsError("Not enough balance for this transaction")
# InsufficientFundsError: Not enough balance for this transaction
Adding a Custom Message
Override __init__ to embed extra context automatically into the error message:
class InsufficientFundsError(Exception):
def __init__(self, balance, amount):
self.balance = balance
self.amount = amount
super().__init__(
f"Cannot withdraw {amount}. Balance is only {balance}."
)
def withdraw(balance, amount):
if amount > balance:
raise InsufficientFundsError(balance, amount)
return balance - amount
try:
withdraw(500, 800)
except InsufficientFundsError as e:
print(f"Error: {e}")
print(f"You have {e.balance}, need {e.amount}")
Error: Cannot withdraw 800. Balance is only 500.
You have 500, need 800
Exception Hierarchy — Group Related Errors
Create a base exception for your app, then specific sub-exceptions. You can catch them individually or all at once using the base:
# Base exception for the whole app
class ShopError(Exception):
pass
# Specific exceptions inherit from ShopError
class OutOfStockError(ShopError):
pass
class InvalidCouponError(ShopError):
pass
class PaymentFailedError(ShopError):
pass
# Catch a specific one
try:
raise OutOfStockError("Rice is out of stock")
except OutOfStockError as e:
print(f"Stock issue: {e}")
# OR catch any shop error at once
try:
raise InvalidCouponError("Coupon SAVE50 has expired")
except ShopError as e:
print(f"Shop error: {e}")
Real Example — User Registration
class RegistrationError(Exception):
pass
class UsernameTakenError(RegistrationError):
def __init__(self, username):
super().__init__(f"Username '{username}' is already taken")
class WeakPasswordError(RegistrationError):
def __init__(self):
super().__init__("Password must be at least 8 characters")
existing_users = ["riya", "arjun"]
def register(username, password):
if username.lower() in existing_users:
raise UsernameTakenError(username)
if len(password) < 8:
raise WeakPasswordError()
print(f"✓ Registered: {username}")
tests = [("Sneha", "secure123"), ("riya", "pass"), ("Dev", "123")]
for user, pwd in tests:
try:
register(user, pwd)
except UsernameTakenError as e:
print(f"✗ {e}")
except WeakPasswordError as e:
print(f"✗ {e}")
✓ Registered: Sneha
✗ Username 'riya' is already taken
✗ Password must be at least 8 characters
"One custom exception class costs you two lines. It buys you crystal-clear error messages, specific catching, and code that reads like natural language."
— ShurAI🧠 Quiz — Q1
What do you write to create the simplest possible custom exception called GameOverError?
🧠 Quiz — Q2
Why is InsufficientFundsError better than ValueError for a bank app?
🧠 Quiz — Q3
If OutOfStockError inherits from ShopError, which except blocks can catch it?
🧠 Quiz — Q4
What does super().__init__("message") do inside a custom exception’s __init__?