
Python
Learn how to create your own exception classes that make your code more expressive and easier to debug.
A custom exception is just a class that inherits from `Exception`:
# Simplest custom exception
class InsufficientFundsError(Exception):
pass
class InvalidAccountError(Exception):
pass
# Use them like built-in exceptions
def withdraw(balance, amount):
if not isinstance(amount, (int, float)):
raise TypeError("Amount must be a number")
if amount < 0:
raise ValueError("Amount cannot be negative")
if amount > balance:
raise InsufficientFundsError(f"Cannot withdraw ${amount}. Balance: ${balance}")
return balance - amount
# Test it
account_balance = 100.00
try:
new_balance = withdraw(account_balance, 50)
print(f"Withdrawal successful. New balance: ${new_balance}")
except InsufficientFundsError as e:
print(f"Transaction failed: {e}")You can store extra information in custom exceptions:
# Custom exception with details
class TransactionError(Exception):
def __init__(self, message, transaction_id=None, amount=None):
super().__init__(message)
self.transaction_id = transaction_id
self.amount = amount
class InsufficientFundsError(TransactionError):
pass
# Use it with extra data
def process_payment(account_id, amount, balance):
if amount > balance:
raise InsufficientFundsError(
f"Cannot charge ${amount}",
transaction_id=f"TXN_{account_id}",
amount=amount
)
return balance - amount
# Catch and access the data
try:
new_balance = process_payment("ACC123", 150, 100)
except InsufficientFundsError as e:
print(f"Error: {e}")
print(f"Transaction ID: {e.transaction_id}")
print(f"Amount: ${e.amount}")Create your own exception hierarchy:
# Base custom exception
class APIError(Exception):
"""Base exception for API-related errors"""
pass
# Specific API errors
class APIConnectionError(APIError):
"""Connection to API failed"""
pass
class APITimeoutError(APIError):
"""API request timed out"""
pass
class APIResponseError(APIError):
"""Received invalid response from API"""
pass
class APIAuthenticationError(APIError):
"""Authentication with API failed"""
pass
# Using the hierarchy
def fetch_user_data(user_id, api_key):
try:
# Simulate API call
if not api_key:
raise APIAuthenticationError("Missing API key")
if user_id < 0:
raise ValueError("User ID must be positive")
# Simulate successful response
return {"id": user_id, "name": f"User {user_id}"}
except APIAuthenticationError:
print("Please provide valid API credentials")
raise
except APIConnectionError:
print("Cannot connect to API. Check your connection.")
raise
except APIError as e:
print(f"API error occurred: {e}")
raise
# Catching different levels
try:
user = fetch_user_data(-1, "key123")
except APIAuthenticationError:
print("Authentication failed - please login again")
except APIError:
print("API error - please try again later")# Well-documented custom exceptions
class ValidationError(Exception):
"""
Raised when user input validation fails.
Attributes:
field (str): The field that failed validation
value: The invalid value
rule (str): The validation rule that was violated
"""
def __init__(self, field, value, rule):
self.field = field
self.value = value
self.rule = rule
message = f"{field} validation failed: {rule} (got: {value})"
super().__init__(message)
class AgeValidationError(ValidationError):
"""Raised when age validation fails."""
pass
class EmailValidationError(ValidationError):
"""Raised when email validation fails."""
pass
# Usage
def validate_age(age):
if not isinstance(age, int):
raise AgeValidationError("age", age, "must be an integer")
if age < 0:
raise AgeValidationError("age", age, "cannot be negative")
if age > 150:
raise AgeValidationError("age", age, "must be realistic")
try:
validate_age("25")
except AgeValidationError as e:
print(f"Error: {e}")
print(f"Field: {e.field}")
print(f"Invalid value: {e.value}")
print(f"Rule broken: {e.rule}")# Example 1: Game exceptions
class GameError(Exception):
pass
class InvalidMoveError(GameError):
def __init__(self, move, reason):
self.move = move
self.reason = reason
super().__init__(f"Invalid move '{move}': {reason}")
class GameOverError(GameError):
def __init__(self, winner):
self.winner = winner
super().__init__(f"Game over! Winner: {winner}")
def make_move(board, position, player):
if position < 0 or position >= len(board):
raise InvalidMoveError(position, "position out of bounds")
if board[position] is not None:
raise InvalidMoveError(position, "position already taken")
board[position] = player
return board
# Test
try:
board = [None] * 9
make_move(board, 10, "X")
except InvalidMoveError as e:
print(f"Error: {e.reason}")
# Example 2: Database exceptions
class DatabaseError(Exception):
pass
class ConnectionError(DatabaseError):
"""Cannot connect to database"""
pass
class QueryError(DatabaseError):
"""Database query failed"""
pass
class DataIntegrityError(DatabaseError):
"""Data violates integrity constraints"""
pass
def execute_query(query, db_connection):
if not db_connection:
raise ConnectionError("No database connection")
if "DROP" in query.upper():
raise DataIntegrityError("Cannot execute destructive queries")
return {"status": "success"}
try:
execute_query("DROP TABLE users", None)
except DatabaseError as e:
print(f"Database error: {e}")
# Example 3: Configuration exceptions
class ConfigurationError(Exception):
"""Base configuration error"""
pass
class ConfigNotFoundError(ConfigurationError):
def __init__(self, key):
self.key = key
super().__init__(f"Configuration key '{key}' not found")
class ConfigValueError(ConfigurationError):
def __init__(self, key, expected_type):
self.key = key
self.expected_type = expected_type
super().__init__(f"Config '{key}' must be {expected_type}")
def get_config(config_dict, key, expected_type):
if key not in config_dict:
raise ConfigNotFoundError(key)
value = config_dict[key]
if not isinstance(value, expected_type):
raise ConfigValueError(key, expected_type.__name__)
return value
try:
config = {"debug": "yes"} # Should be boolean
debug_mode = get_config(config, "debug", bool)
except ConfigValueError as e:
print(f"Config error: {e}")# ✅ Good: Specific, descriptive, inherited from Exception
class FileProcessingError(Exception):
"""Base exception for file processing"""
pass
class FileNotFoundError(FileProcessingError):
"""File doesn't exist"""
pass
class FilePermissionError(FileProcessingError):
"""Don't have permission to access file"""
pass
# ❌ Avoid: Inheriting from wrong class
# class MyException(ValueError): # Confusing!
# pass
# ✅ Good: Meaningful names that describe the error
class InvalidCredentialsError(Exception):
pass
# ❌ Avoid: Generic or unclear names
# class Error(Exception):
# pass
# ✅ Good: Provide context in error messages
def process_file(filename):
try:
with open(filename) as f:
return f.read()
except FileNotFoundError:
raise FileProcessingError(f"Cannot find {filename} in current directory")
# ❌ Avoid: Confusing error messages
# raise FileProcessingError("error")Resources
Ojasa Mirai
Master AI-powered development skills through structured learning, real projects, and verified credentials. Whether you're upskilling your team or launching your career, we deliver the skills companies actually need.
Learn Deep • Build Real • Verify Skills • Launch Forward