In this tutorial, we will learn how we can create our own customized and user-defined exceptions in Python.

1. What are exceptions and how to handle them?

To know more about exceptions in Python and how we can handle them using several keywords like try, except, else, finally and raise, go through this tutorial.

Now we have knowledge of how to use these keywords and built-in Exceptions in our code to handle them. But there are chances that software may require exceptions other than the built-in ones and developer has to define own exceptions according to the requirement. In such scenarios, we create our own Exception classes and handle them similarly as we handle built-in exceptions. Let’s look at the hierarchy of built-in exceptions in Python before creating our own exceptions.

2. Built-in Exceptions Class hierarchy

BaseException 
+-- SystemExit 
+-- KeyboardInterrupt 
+-- GeneratorExit 
+-- Exception # Base Class of all exceptions 
    +-- StopIteration 
    +-- StopAsyncIteration 
    +-- ArithmeticError 
    |   +-- FloatingPointError 
    |   +-- OverflowError 
    |   +-- ZeroDivisionError 
    +-- AssertionError 
    +-- AttributeError 
    +-- BufferError 
    +-- EOFError 
    +-- ImportError 
    |   +-- ModuleNotFoundError
    +-- LookupError 
    |   +-- IndexError 
    |   +-- KeyError 
    +-- MemoryError     
    +-- NameError 
    |   +-- UnboundLocalError 
    +-- OSError 
    |   +-- BlockingIOError 
    |   +-- ChildProcessError
    |   +-- ConnectionError 
    |   |   +-- BrokenPipeError 
    |   |   +-- ConnectionAbortedError 
    |   |   +-- ConnectionRefusedError 
    |   |   +-- ConnectionResetError 
    |   +-- FileExistsError 
    |   +-- FileNotFoundError 
    |   +-- InterruptedError 
    |   +-- IsADirectoryError 
    |   +-- NotADirectoryError 
    |   +-- PermissionError 
    |   +-- ProcessLookupError 
    |   +-- TimeoutError 
    +-- ReferenceError 
    +-- RuntimeError 
    |   +-- NotImplementedError 
    |   +-- RecursionError 
    +-- SyntaxError 
    |   +-- IndentationError 
    |   +-- TabError 
    +-- SystemError 
    +-- TypeError 
    +-- ValueError 
    |   +-- UnicodeError 
    |   +-- UnicodeDecodeError 
    |   +-- UnicodeEncodeError 
    |   +-- UnicodeTranslateError 
    +-- Warning 
        +-- DeprecationWarning
        +-- PendingDeprecationWarning 
        +-- RuntimeWarning 
        +-- SyntaxWarning 
        +-- UserWarning 
        +-- FutureWarning 
        +-- ImportWarning 
        +-- UnicodeWarning 
        +-- BytesWarning 
        +-- ResourceWarning

3. Creating User-defined Exceptions Classes

From the above hierarchy, we have seen that base class of almost all exceptions in Python directly or indirectly is the class Exception and if we want to create our own exception, we have to inherit this Exception class or its subclasses in the newly created custom exception class.

3.1 Creating a simple user-defined exception class

Lets defined a simple custom exception class by deriving in-built Exception class  and raise the exception by using the raise keyword.

class CustomException(Exception):
    pass
raise CustomException("Message related to exception")

Output:

Traceback (most recent call last):
  File "cutomexcp.py", line 4, in 
    raise CustomException("Message related to exception")
__main__.CustomException: Message related to exception

3.2 Creating a user-defined exception class using magic methods

To define a custom exception class, we generally use two magic methods, __init__() and __str__(), __init__() is a constructor of a class that will be called automatically when an object of that class is created and __str__() is automatically called whenever the object is to be printed. So we will modify these two magic methods according to the requirements.

Scenario: Suppose we want to create a simple division program but it cannot divide negative numbers as it may introduce some bugs. So have to create a custom exception class that will produce an exception in runtime whenever the user enters a negative integer either in Numerator or denominator. We will do all this step by step.

3.2.1 Create a Custom base exception class that will inherit Exception class

class CustomBaseException(Exception):
    pass

3.2.2 Creating our main Custom exception class

Let us create a new class named NegativeDivisionErrorwhich will inherit the CustomBaseException class and will contain the main code for our scenario. First, we have to define the __init__() method that will take the 2 arguments self and *args, that will take all the arguments as a tuple that are passed when raising the exception of this class

class NegativeDivisionError(CustomBaseException):
    def __init__(self,*args):
        if args[0]<0:
            self.res="NegativeDivisionError Exception: Numerator Cannot be Negative"
        elif args[1]<0:
            self.res="NegativeDivisionError Exception: Denominator Cannot be Negative"

3.2.3 Defining __str__() method

Now we have defined __init__() method, its time to define __Str__() method that will define the print behaviour of our exception class. Basically, if we got any res then it will return it.

def __str__(self):
        if self.res:
            return self.res

3.2.4 Complete code

class CustomBaseException(Exception):
    pass
class NegativeDivisionError(CustomBaseException):
    def __init__(self,*args):
        if args[0]<0:
            self.res="NegativeDivisionError Exception: Numerator Cannot be Negative"
        elif args[1]<0:
            self.res="NegativeDivisionError Exception: Denominator Cannot be Negative"
    def __str__(self):
        if self.res:
            return self.res
try:
    a = int(input("Enter Numerator: "))
    b = int(input("Enter Denominator: "))
    if a<0 or b<0:
        raise NegativeDivisionError(a,b)
    c=a/b
except NegativeDivisionError as nde:
    print(nde)
else:
    print("Result of division is:",c)

So we have taken two integers from the user and if any of them is negative we will raise an exception passing both integers to our created NegativeDivisionError class instance. If the condition evaluates to be true then the exception will print as defined inside except block else we will print the output of the division using else block.

Let us run the code on different inputs.

Output 1: 

Enter Numerator: 5
Enter Denominator: -3
NegativeDivisionError Exception: Denominator Cannot be Negative

Output 2:

Enter Numerator: -9
Enter Denominator: 3
NegativeDivisionError Exception: Numerator Cannot be Negative

Output 3:

Enter Numerator: -7
Enter Denominator: -3
NegativeDivisionError Exception: Numerator Cannot be Negative

Output 4:

Enter Numerator: 6
Enter Denominator: 2
Result of division is: 3.0

In this tutorial we see how we can create our own exception classes to handle cases as per the requirement, If you have any doubt, feel free to ask in the comment section.

Resources:

Happy Learning 🙂