How to Create Custom Exceptions in Python

Python’s built-in exception classes do not address certain error situations that may arise in your code. In such cases, you’ll need to create custom exceptions to handle these errors effectively.
In Python, you can define custom exceptions and raise them when specific error situations occur. You can manage specific, informative errors with custom exceptions, improving your code’s readability and maintainability.
Why Do You Need Custom Exceptions?
During the development of an application, various error scenarios can arise due to changes in the code, integration with other packages or libraries, and interactions with external apps. It is crucial to handle these errors to recover from them or handle failure gracefully.
Python offers a range of built-in exception classes that cover errors such as ValueError, TypeError, FileNotFoundError, and more. While these built-in exceptions serve their purpose well, they may only sometimes accurately represent the errors that can occur in your application.
By creating custom exceptions, you can tailor them specifically to suit the requirements of your application and provide information for developers who utilize your code.
How to Define Custom Exceptions
To create custom exceptions, define a Python class that inherits from the Exception class. The Exception class offers base functionality you’ll need to handle exceptions, and you can customize it to add features based on your specific needs.
When creating custom exception classes, keep them simple while including necessary attributes for storing error information. Exception handlers can then access these attributes to handle errors appropriately.
Here’s a custom exception class, MyCustomError:
class MyCustomError(Exception):
def __init__(self, message=None):
self.message = message
super().__init__(message)
This class accepts an optional message argument during initialization. It uses the super() method to call the constructor of the base Exception class, which is essential for exception handling.
How to Raise Custom Exceptions
To raise an error, use the raise keyword followed by an instance of your custom exception class, passing it an error message as an argument:
if True:
raise MyCustomError("A Custom Error Was Raised!!!.")
You can also raise the error without passing any arguments:
if True:
raise MyCustomError
Either format is suitable for raising custom errors.
How to Handle Custom Exceptions
Handling custom exceptions follows the same approach as handling built-in exceptions. Use try, except, and finally blocks to catch custom exceptions and take appropriate action.
try:
print("Hello, You're learning how to MakeUseOf Custom Errors")
raise MyCustomError("Opps, Something Went Wrong!!!.")
except MyCustomError as err:
print(f"Error: {err}")
finally:
print("Done Handling Custom Error")
This way, you can handle all forms of custom exceptions raised.
If an exception occurs during the execution of a try block, a corresponding except block can catch and handle it. If there is no appropriate except block to handle the exception, any finally block will execute, followed by the exception raising again. Use a finally block primarily to perform clean-up tasks that must run in any circumstances, whether an exception occurs or not.
try:
raise KeyboardInterrupt
except MyCustomError as err:
print(f"Error: {err}")
finally:
print("Did not Handle the KeyboardInterrupt Error.
Can Only Handle MyCustomError")
In this sample, a KeyboardInterrupt exception occurs, but the except block only handles MyCustomError exceptions. In this case, the finally block runs, and then the unhandled exception raises again.
Inheriting Custom Error Classes
Based on the concept of Object-Oriented Programming (OOP), you can also inherit from custom exception classes, just like regular classes. By inheriting from a custom exception class, you can create error classes that provide more specific context to an exception. This approach allows you to handle errors at different levels in your code and provides a better understanding of what caused the error.
Say you’re developing a web application that interacts with an external API. This API might have different error scenarios. You’ll want to handle these errors consistently and clearly throughout your code. To achieve this, create a custom exception class, BaseAPIException:
class BaseAPIException(Exception):
"""Base class for API-related exceptions."""
def __init__(self, message):
super().__init__(message)
self.message = message
Once you have this base custom exception class, you can create child exception classes that inherit from it:
class APINotFoundError(BaseAPIException):
"""Raised when the requested resource is not found in the API."""
passclass APIAuthenticationError(BaseAPIException):
"""Raised when there's an issue with authentication to the API."""
pass
class APIRateLimitExceeded(BaseAPIException):
"""Raised when the rate limit for API requests is exceeded."""
pass
Raise and catch these custom exceptions when making calls to the API within your web application. Handle them accordingly using the appropriate logic in your code.
def request_api():
try:
raise APINotFoundError("Requested resource not found.")
except APINotFoundError as err:
print(f"API Not Found Error: {err}")
except APIAuthenticationError:
print(f"API Authentication Error: {err}")
except APIRateLimitExceeded:
print(f"API Rate Limit Exceeded: {err}")
except BaseAPIException:
print(f"Unknown API Exception: {err}")
The final except clause checks against the parent class and acts as a catch-all for any other API-related errors.
When you inherit custom exception classes, you can effectively handle errors within the API. This approach allows you to separate your error handling from the API’s implementation details, making it easier to add custom exceptions or make changes as the API evolves or encounters new error cases.
Wrapping Custom Exceptions
To wrap exceptions means to catch an exception, encapsulate it within a custom exception, and then raise that custom exception while referencing the original exception as its cause. This technique helps provide context to error messages and keeps implementation details hidden from the calling code.
Consider the scenario where your web app interacts with an API. If the API raises a LookupError, you can catch it, then raise a custom APINotFoundError exception that references the LookupError as its cause:
def request_api():
try:
raise LookupError("Sorry, You Encountered A LookUpError !!!")
except LookupError as original_exception:
try:
raise APINotFoundError
("Requested resource not found.") from original_exception
except APINotFoundError as wrapped_exception:
print(f"Caught wrapped API exception: {wrapped_exception}")
raise
try:
request_api()
except APINotFoundError as err:
print(f"Caught API exception: {err.__cause__}")
Use a from clause with the raise statement to reference the original exception within your custom exception.
When the custom exception occurs, it includes the original exception as a __cause__ attribute, providing a link between the custom exception and the original. This lets you trace the origin of an exception.
By wrapping exceptions, you can provide more meaningful context and send more appropriate error messages to users, without revealing internal implementation details of your code or the API. It also lets you manage and address types of errors in a structured and uniform way.
Customizing Class Behavior in Python
By inheriting the base exception class that Python provides, you can create simple and useful exceptions that you can raise when specific errors occur in your code. You can also implement custom behavior for your exception classes with the help of magic or dunder methods.