Readers like you help support MUO. When you make a purchase using links on our site, we may earn an affiliate commission. Read More.

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?

own your error

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 # shorthand

Either format is suitable for raising custom errors.

raised custom error

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.

handling custom error

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.

A stacktrace showing an unhandled exception

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."""
    pass

class 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:
        # Simulate an API error for demonstration purposes
        raise APINotFoundError("Requested resource not found.")
    except APINotFoundError as err:
        # Log or handle the 'Not Found' error case
        print(f"API Not Found Error: {err}")
    except APIAuthenticationError:
        # Take appropriate actions for authentication error
        print(f"API Authentication Error: {err}")
    except APIRateLimitExceeded:
        # Handle the rate limit exceeded scenario
        print(f"API Rate Limit Exceeded: {err}")
    except BaseAPIException:
        # Handle other unknown API exceptions
        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.

inheriting custom exception

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:
        # Simulate an API error for demonstration purposes
        # Assuming the external API raised a LookupError
        raise LookupError("Sorry, You Encountered A LookUpError !!!")
    except LookupError as original_exception:
        try:
            # Wrap the original exception with a custom exception
            raise APINotFoundError \
                 ("Requested resource not found.") from original_exception
        except APINotFoundError as wrapped_exception:
            # Handle the wrapped exception here
            print(f"Caught wrapped API exception: {wrapped_exception}")

            # or re-raise it if necessary
            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.

wrapping 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.