Python Custom Exceptions
Table of Contents
In the last lesson, we learned about Python raise Statement. Now let’s create custom exceptions that clearly describe our program’s errors.
Let’s Talk About Making Our Own Exceptions
We know about exceptions. Python has many built-in ones. ValueError, TypeError, IndexError, KeyError, and so on.
But sometimes, none of them fit. We have a specific error for our program. And we want to raise it. With a name that makes sense for our situation.
That is what custom exceptions are for.
What Is a Custom Exception in Python?
A custom exception is an exception we define ourselves. We give it a name. And we use it like any other exception.
It is like creating our own error type. One that describes exactly what went wrong in our program.
Why Create Custom Exceptions in Python?
Here are some reasons.
First, clarity. If we raise a ValueError, it is vague. But if we raise a NegativeAgeError, anyone reading the code knows exactly what happened.
Second, we can catch our own exceptions specifically. Without catching other unrelated errors.
Third, it makes our code more organized. We can have a hierarchy of exceptions for our application.
How to Create a Custom Exception in Python
We create a custom exception by defining a class that inherits from Exception or one of its subclasses.
class MyError(Exception): passThat is it. A simple class. It inherits from Exception. And it does nothing else. But it is now a valid exception.
We can raise it.
raise MyError("Something went wrong")And we can catch it.
try: raise MyError("Something went wrong")except MyError as e: print(f"Caught: {e}")Output
Caught: Something went wrongThat is the basic pattern.
Python Custom Exceptions: A Real-Life Example
Let’s say we are building a banking application. We want a specific error for insufficient funds.
class InsufficientFundsError(Exception): pass
def withdraw(balance, amount): if amount > balance: raise InsufficientFundsError(f"Cannot withdraw {amount}. Balance is {balance}") return balance - amount
try: new_balance = withdraw(100, 150) print(f"New balance: {new_balance}")except InsufficientFundsError as e: print(f"Error: {e}")Output
Error: Cannot withdraw 150. Balance is 100That is very clear. Anyone reading the code knows exactly what happened.
Adding More Details to the Exception in Python
We can give our exception class more features. Like storing extra information.
class InsufficientFundsError(Exception): def __init__(self, balance, amount): self.balance = balance self.amount = amount super().__init__(f"Cannot withdraw {amount}. Balance is {balance}")Now we can access the balance and amount from the exception object.
try: raise InsufficientFundsError(100, 150)except InsufficientFundsError as e: print(f"Balance: {e.balance}") print(f"Amount: {e.amount}") print(f"Message: {e}")Output
Balance: 100Amount: 150Message: Cannot withdraw 150. Balance is 100That is very useful when we want to handle the error in a specific way.
A Hierarchy of Custom Exceptions in Python
We can create a base exception for our application. And then more specific ones that inherit from it.
class AppError(Exception): pass
class InputError(AppError): pass
class ProcessingError(AppError): pass
class OutputError(AppError): passNow we can catch all app errors with AppError. Or catch specific ones.
try: raise InputError("Invalid input")except InputError as e: print(f"Input error: {e}")except AppError as e: print(f"App error: {e}")Output
Input error: Invalid inputThat is a clean way to organize exceptions.
When to Create Custom Exceptions in Python
Create custom exceptions when:
- We have a specific error that does not fit a built-in one.
- We want to catch our own errors separately from others.
- We want to give more context about what went wrong.
- We are building a library or framework.
Let’s not create custom exceptions for every little thing. We use built-in exceptions when they fit. Only create custom ones when they add value.
A Real-Life Example with a Validation System
Let’s build a simple form validator.
class ValidationError(Exception): pass
class RequiredFieldError(ValidationError): pass
class InvalidEmailError(ValidationError): pass
def validate_email(email): if not email: raise RequiredFieldError("Email is required") if "@" not in email: raise InvalidEmailError(f"'{email}' is not a valid email") return True
try: validate_email("")except RequiredFieldError as e: print(f"Required: {e}")except InvalidEmailError as e: print(f"Invalid: {e}")
try: validate_email("invalid")except RequiredFieldError as e: print(f"Required: {e}")except InvalidEmailError as e: print(f"Invalid: {e}")
try: validate_email("user@example.com") print("Email is valid")except ValidationError as e: print(f"Validation error: {e}")Output
Required: Email is requiredInvalid: 'invalid' is not a valid emailEmail is validThat shows how we can use different custom exceptions for different validation errors. And catch them separately.
Custom Exceptions with Multiple Inheritance in Python
We can also combine exceptions. For example, if we want our exception to be both a ValueError and a custom one.
class MyError(ValueError): passNow MyError is a subclass of ValueError. So catching ValueError will also catch MyError.
try: raise MyError("Custom error")except ValueError as e: print(f"Caught as ValueError: {e}")Output
Caught as ValueError: Custom errorThat can be useful when we want compatibility with existing code.
Common Mistakes with Python Custom Exceptions
Mistake 1: Forgetting to Inherit from Exception
class MyError: # Not an exception pass
raise MyError("Error") # Error!So what do we do? We inherit from Exception or a subclass.
class MyError(Exception): passMistake 2: Not Calling super().init()
class MyError(Exception): def __init__(self, message): self.message = messageThis works, but the base Exception class is not initialized. So str(e) might not work as expected.
So what do we do? We call super().__init__(message).
class MyError(Exception): def __init__(self, message): super().__init__(message) self.message = messageMistake 3: Creating Too Many Custom Exceptions
Let’s not make a custom exception for every possible error. We use built-in ones when they fit. Only create custom ones when we need to catch them specifically or add context.
Mistake 4: Catching Only the Base Class
try: raise InputError("Bad input")except AppError as e: # Catches InputError too print("App error")If we catch the base class, we catch all subclasses. That might be what we want. But if we want to handle them differently, we catch the specific ones.
Next, we will learn about Python Introduction to Files and see how programs save and retrieve information.