What is a Custom Exception?
A custom exception is a user-defined exception class created to represent a specific error or business rule in an application.
Python provides many built-in exceptions such as ValueError, TypeError, KeyError, and FileNotFoundError. However, real applications often need errors that are specific to their own business logic.
Built-in Exception
Python-provided errors such as ValueError, TypeError and ZeroDivisionError.
Custom Exception
Developer-created exception classes representing application-specific errors.
raise
Used to intentionally trigger an exception when a business rule is violated.
try-except
Used to catch and handle the custom exception without crashing the application.
A custom exception can be created by inheriting from Python's built-in Exception class.
class MyCustomError(Exception):
pass
try:
raise MyCustomError("Something went wrong")
except MyCustomError as e:
print("Error:", e)
Error: Something went wrong
Here, MyCustomError is a user-defined exception.
Custom exceptions make application errors easier to understand and allow developers to separate different business rules.
| Situation | Possible Custom Exception |
|---|---|
| Insufficient bank balance | InsufficientBalanceError |
| Invalid age | InvalidAgeError |
| Product out of stock | OutOfStockError |
| Invalid employee ID | InvalidEmployeeError |
| Payment failure | PaymentFailedError |
A custom exception can receive a meaningful error message.
class InvalidAgeError(Exception):
pass
age = 15
try:
if age < 18:
raise InvalidAgeError("Age must be 18 or above")
except InvalidAgeError as e:
print("Error:", e)
Error: Age must be 18 or above
A bank application can create a custom exception when a customer tries to withdraw more money than the available balance.
class InsufficientBalanceError(Exception):
pass
class BankAccount:
def __init__(self, balance):
self.balance = balance
def withdraw(self, amount):
if amount > self.balance:
raise InsufficientBalanceError(
"Insufficient balance for this withdrawal"
)
self.balance -= amount
print("Withdrawal successful")
print("Remaining balance:", self.balance)
account = BankAccount(5000)
try:
account.withdraw(7000)
except InsufficientBalanceError as e:
print("Transaction failed:", e)
Transaction failed: Insufficient balance for this withdrawal
A custom exception can have its own constructor so that it can store additional information.
class ProductNotFoundError(Exception):
def __init__(self, product_id):
self.product_id = product_id
super().__init__(
f"Product {product_id} was not found"
)
try:
raise ProductNotFoundError(101)
except ProductNotFoundError as e:
print(e)
print("Product ID:", e.product_id)
Product 101 was not found
Product ID: 101
An HR application may reject an employee record if the salary or employee age does not satisfy company rules.
class InvalidEmployeeError(Exception):
pass
def validate_employee(name, age, salary):
if age < 18:
raise InvalidEmployeeError(
"Employee must be at least 18 years old"
)
if salary < 15000:
raise InvalidEmployeeError(
"Salary is below the minimum allowed amount"
)
print("Employee validation successful")
try:
validate_employee("Rahul", 25, 12000)
except InvalidEmployeeError as e:
print("Validation Error:", e)
Validation Error: Salary is below the minimum allowed amount
Custom exceptions can store useful information such as an order ID, requested quantity, and available quantity.
class OutOfStockError(Exception):
def __init__(self, product, requested, available):
self.product = product
self.requested = requested
self.available = available
message = (
f"{product}: requested {requested}, "
f"but only {available} available"
)
super().__init__(message)
try:
raise OutOfStockError("Laptop", 5, 2)
except OutOfStockError as e:
print("Order Error:", e)
print("Product:", e.product)
print("Requested:", e.requested)
print("Available:", e.available)
Order Error: Laptop: requested 5, but only 2 available
Product: Laptop
Requested: 5
Available: 2
An e-commerce system can use a custom exception when a customer tries to order more products than are available.
class OutOfStockError(Exception):
pass
class Product:
def __init__(self, name, stock):
self.name = name
self.stock = stock
def buy(self, quantity):
if quantity > self.stock:
raise OutOfStockError(
f"Only {self.stock} units of {self.name} are available"
)
self.stock -= quantity
print("Purchase successful")
print("Remaining stock:", self.stock)
product = Product("Headphones", 3)
try:
product.buy(5)
except OutOfStockError as e:
print("Order failed:", e)
Order failed: Only 3 units of Headphones are available
Custom exceptions can inherit from another custom exception. This creates an exception hierarchy.
class ApplicationError(Exception):
pass
class PaymentError(ApplicationError):
pass
class CardPaymentError(PaymentError):
pass
try:
raise CardPaymentError("Card payment was declined")
except CardPaymentError as e:
print("Card Error:", e)
except PaymentError as e:
print("Payment Error:", e)
except ApplicationError as e:
print("Application Error:", e)
Card Error: Card payment was declined
A payment system can define different custom exceptions for different payment failures.
class PaymentError(Exception):
pass
class PaymentDeclinedError(PaymentError):
pass
class PaymentLimitError(PaymentError):
pass
def process_payment(amount):
if amount > 100000:
raise PaymentLimitError(
"Payment exceeds the allowed limit"
)
if amount == 0:
raise PaymentDeclinedError(
"Payment amount cannot be zero"
)
print("Payment processed successfully")
try:
process_payment(150000)
except PaymentLimitError as e:
print("Limit Error:", e)
except PaymentDeclinedError as e:
print("Payment Error:", e)
Limit Error: Payment exceeds the allowed limit
Custom exceptions can be combined with all major exception handling blocks.
class InvalidAmountError(Exception):
pass
amount = 500
try:
if amount <= 0:
raise InvalidAmountError(
"Amount must be greater than zero"
)
except InvalidAmountError as e:
print("Error:", e)
else:
print("Amount is valid")
finally:
print("Validation completed")
Amount is valid
Validation completed
Sometimes an exception is caught, logged, and then raised again so that another part of the application can handle it.
class DatabaseError(Exception):
pass
def connect_database():
try:
raise ConnectionError("Database connection failed")
except ConnectionError as e:
print("Logging database error:", e)
raise DatabaseError("Unable to connect to database")
try:
connect_database()
except DatabaseError as e:
print("Application Error:", e)
Logging database error: Database connection failed
Application Error: Unable to connect to database
Functions can raise custom exceptions when input does not satisfy application-specific rules.
class InvalidUsernameError(Exception):
pass
def register_user(username):
if len(username) < 5:
raise InvalidUsernameError(
"Username must contain at least 5 characters"
)
print("User registered successfully")
try:
register_user("Tom")
except InvalidUsernameError as e:
print("Registration Error:", e)
Registration Error: Username must contain at least 5 characters
A login application can use custom exceptions for invalid credentials and account lock situations.
class LoginError(Exception):
pass
class InvalidCredentialsError(LoginError):
pass
class AccountLockedError(LoginError):
pass
def login(username, password):
if username == "admin" and password == "12345":
print("Login successful")
return
raise InvalidCredentialsError(
"Invalid username or password"
)
try:
login("admin", "wrong")
except InvalidCredentialsError as e:
print("Login Failed:", e)
except AccountLockedError as e:
print("Account Locked:", e)
Login Failed: Invalid username or password
A file-processing application can define a custom exception when an uploaded file does not meet requirements.
class InvalidFileError(Exception):
pass
def validate_file(filename):
if not filename.endswith(".csv"):
raise InvalidFileError(
"Only CSV files are allowed"
)
print("File accepted")
try:
validate_file("employees.pdf")
except InvalidFileError as e:
print("File Error:", e)
File Error: Only CSV files are allowed
This example combines multiple custom exceptions in an ATM application.
class ATMError(Exception):
pass
class InsufficientBalanceError(ATMError):
pass
class InvalidPINError(ATMError):
pass
class InvalidAmountError(ATMError):
pass
class ATM:
def __init__(self, pin, balance):
self.pin = pin
self.balance = balance
def withdraw(self, entered_pin, amount):
if entered_pin != self.pin:
raise InvalidPINError("Incorrect PIN")
if amount <= 0:
raise InvalidAmountError(
"Withdrawal amount must be greater than zero"
)
if amount > self.balance:
raise InsufficientBalanceError(
"Insufficient account balance"
)
self.balance -= amount
print("Withdrawal successful")
print("Remaining balance:", self.balance)
atm = ATM(1234, 10000)
try:
atm.withdraw(1234, 12000)
except InvalidPINError as e:
print("PIN Error:", e)
except InvalidAmountError as e:
print("Amount Error:", e)
except InsufficientBalanceError as e:
print("Balance Error:", e)
Balance Error: Insufficient account balance
Custom exceptions follow normal Python inheritance rules. This allows common application errors to be caught together.
class OrderError(Exception):
pass
class InvalidOrderError(OrderError):
pass
class OrderCancelledError(OrderError):
pass
try:
raise InvalidOrderError("Invalid order details")
except OrderError as e:
print("Order problem:", e)
Order problem: Invalid order details
An employee management system can raise a custom exception when an employee requests more leave than available.
class LeaveError(Exception):
pass
def apply_leave(employee, requested_days, available_days):
if requested_days > available_days:
raise LeaveError(
f"{employee} has only {available_days} leave days available"
)
print("Leave approved")
try:
apply_leave("Anita", 8, 5)
except LeaveError as e:
print("Leave Request Failed:", e)
Leave Request Failed: Anita has only 5 leave days available
Applications sometimes store an error code along with the human-readable error message.
class APIError(Exception):
def __init__(self, code, message):
self.code = code
self.message = message
super().__init__(message)
try:
raise APIError(404, "Customer not found")
except APIError as e:
print("Error Code:", e.code)
print("Message:", e.message)
Error Code: 404
Message: Customer not found
- Inherit custom exceptions from Exception.
- Give the exception a meaningful name ending with Error.
- Use custom exceptions for application-specific business rules.
- Provide clear and useful error messages.
- Store important error information as exception attributes when necessary.
- Create an exception hierarchy for related application errors.
- Catch specific exceptions before broader parent exceptions.
- Do not use custom exceptions as a replacement for normal program logic.
- Use raise when a business rule is violated.
- Keep exception classes simple and focused.
| Application | Custom Exception | Purpose |
|---|---|---|
| Banking | InsufficientBalanceError | Insufficient funds |
| E-Commerce | OutOfStockError | Product unavailable |
| Authentication | InvalidCredentialsError | Invalid login details |
| HR | InvalidEmployeeError | Invalid employee information |
| Payments | PaymentFailedError | Payment processing failure |
| Files | InvalidFileError | Invalid uploaded file |
| Orders | InvalidOrderError | Invalid order details |
The following example demonstrates how custom exceptions can be used together in a realistic online shopping workflow.
class OrderError(Exception):
pass
class ProductNotFoundError(OrderError):
pass
class OutOfStockError(OrderError):
pass
class PaymentFailedError(OrderError):
pass
products = {
"Laptop": 2,
"Phone": 5
}
def place_order(product, quantity, payment):
if product not in products:
raise ProductNotFoundError(
f"{product} does not exist"
)
if quantity > products[product]:
raise OutOfStockError(
f"Only {products[product]} units are available"
)
if payment <= 0:
raise PaymentFailedError(
"Payment amount is invalid"
)
products[product] -= quantity
print("Order placed successfully")
print("Product:", product)
print("Quantity:", quantity)
try:
place_order("Laptop", 3, 50000)
except ProductNotFoundError as e:
print("Product Error:", e)
except OutOfStockError as e:
print("Stock Error:", e)
except PaymentFailedError as e:
print("Payment Error:", e)
Stock Error: Only 2 units are available
The basic workflow is:
- Receive input or perform an application operation.
- Check a business-specific rule.
- Use raise when the rule is violated.
- Catch the custom exception using except.
- Display, log, or otherwise handle the error safely.
| Rule | Explanation |
|---|---|
| Inherit from Exception | Custom exceptions should normally extend Exception. |
| Use meaningful names | Names should clearly describe the error. |
| Use raise | Raise the exception when the business rule fails. |
| Use try-except | Catch and handle the custom exception safely. |
| Store useful data | Exception attributes can hold IDs, amounts, codes, etc. |
| Create hierarchies | Related exceptions can inherit from a common parent. |
| Keep errors specific | Use the most specific exception that describes the problem. |