Custom Exceptions¶
Python allows creating custom exception classes by subclassing the built-in Exception class. Custom exceptions improve code clarity and enable specific error handling.
Mental Model
A custom exception is just a class that inherits from Exception. It gives a meaningful name to a specific failure condition so callers can catch it precisely. Start with a bare subclass (class MyError(Exception): pass) and add attributes only when the handler needs structured data about what went wrong.
Basic Custom Exception¶
Create a custom exception by inheriting from Exception.
```python class TooHotError(Exception): pass
def check_temperature(temp): if temp > 60: raise TooHotError print("Temperature is fine.")
try: check_temperature(65) except TooHotError: print("Too hot!") ```
Output:
Too hot!
Custom Exception with Message¶
Pass a message to provide context about the error.
```python class TooHotError(Exception): pass
def check_temperature(temp): if temp > 60: raise TooHotError("Temperature exceeds safe limit.") print("Temperature is fine.")
try: check_temperature(65) except TooHotError as e: print(e) ```
Output:
Temperature exceeds safe limit.
Custom Exception with init¶
Override __init__ for custom initialization.
```python class TooHotError(Exception): def init(self, msg): self.msg = msg
def __str__(self):
return self.msg
raise TooHotError("Too hot for a walk.") ```
Using super().init¶
The preferred approach uses super() to initialize the parent class.
```python class TooHotError(Exception): def init(self, msg): super().init(msg)
try: raise TooHotError("Temperature is 65°F, too hot!") except TooHotError as e: print(e) ```
Output:
Temperature is 65°F, too hot!
Practical Example¶
A class that raises custom exceptions based on conditions.
```python class TooHotError(Exception): def init(self, msg): super().init(msg)
class Walk: def init(self, temperature): self.temperature = temperature
def walk(self):
if self.temperature > 60:
raise TooHotError("Too hot for a walk.")
print("Enjoy walking!")
aug_7_2023 = Walk(65)
try: aug_7_2023.walk() except TooHotError as e: print(e) ```
Output:
Too hot for a walk.
When to Create Custom Exceptions¶
Create custom exceptions when:
- Built-in exceptions don't describe the error adequately
- You need to catch specific errors in your application
- You want to provide domain-specific error messages
- You need to add custom attributes to the exception
python
class InsufficientFundsError(Exception):
def __init__(self, balance, amount):
self.balance = balance
self.amount = amount
super().__init__(
f"Cannot withdraw ${amount}. Balance is ${balance}."
)
Exception Hierarchy for Custom Classes¶
Create a hierarchy of related exceptions.
```python class WeatherError(Exception): """Base class for weather-related errors.""" pass
class TooHotError(WeatherError): pass
class TooColdError(WeatherError): pass
Catch all weather errors¶
try: raise TooHotError("Heat wave!") except WeatherError as e: print(f"Weather issue: {e}") ```
Exercises¶
Exercise 1.
Create a custom exception hierarchy for a banking application: BankError (base), InsufficientFundsError, and AccountNotFoundError. Each should accept a meaningful message.
Solution to Exercise 1
```python
class BankError(Exception):
pass
class InsufficientFundsError(BankError):
def __init__(self, balance, amount):
self.balance = balance
self.amount = amount
super().__init__(
f"Cannot withdraw ${amount}: only ${balance} available"
)
class AccountNotFoundError(BankError):
def __init__(self, account_id):
self.account_id = account_id
super().__init__(f"Account {account_id} not found")
```
Custom exceptions inherit from a common base class, enabling both specific and broad catching.
Exercise 2.
Write a class BankAccount with methods deposit(amount) and withdraw(amount). The withdraw method should raise InsufficientFundsError (from Exercise 1) if the balance would go negative. Include the current balance and requested amount in the error message.
Solution to Exercise 2
```python
class BankAccount:
def __init__(self, balance=0):
self.balance = balance
def deposit(self, amount):
self.balance += amount
def withdraw(self, amount):
if amount > self.balance:
raise InsufficientFundsError(self.balance, amount)
self.balance -= amount
account = BankAccount(100)
account.deposit(50)
print(account.balance) # 150
try:
account.withdraw(200)
except InsufficientFundsError as e:
print(e) # Cannot withdraw $200: only $150 available
```
The exception carries context (balance and amount) that helps with debugging and user feedback.
Exercise 3.
Write a try/except block that catches InsufficientFundsError specifically, then a broader BankError, demonstrating that exception hierarchy ordering matters in except clauses.
Solution to Exercise 3
```python
account = BankAccount(50)
try:
account.withdraw(100)
except InsufficientFundsError as e:
print(f"Specific: {e}")
except BankError as e:
print(f"General: {e}")
```
The more specific exception must come first. If BankError were listed first, it would catch InsufficientFundsError (since it is a subclass), and the specific handler would never execute.