Custom Exception In Python: Everything You Need To Know!

In this article, let us learn all about custom exceptions in Python. In particular, we will try to answer the following questions

  • what are custom exceptions?
  • how do you create one?
  • which exception to inherit from?
  • How to choose between a custom exception vs an in-built exception?
  • and a few ideas to customize an exception.

Let’s begin by addressing what are custom exceptions.

What are custom exceptions?

In simple words, a “custom exception” a.k.a a “user-defined exception” is an exception that you yourself have defined and designed (as opposed to built-in exceptions).

Custom exceptions can be a good way for programmers to deal with the custom/specific problems that arise in their program. In other words, custom exceptions are tailor-made to provide a more specific context to the problem at hand.

For example, consider a function that calculates the area of a rectangle given its length and breadth.

def calcArea(length, breadth):
    return length*breadth

As you know, lengths and breadths (dimensions that make up the rectangle) cannot be 0 or negative numbers. If the user of our code gives a bad input to our calc_area function, then we need to deal with those.

One option is to use the built-in ValueError exception as shown below.

def calcArea(length, breadth):
    if (length <= 0) or (breadth <= 0):
        raise ValueError
    else:
        return length*breadth

If you wish to learn more about ValueError and how to solve it like a pro, I suggest reading the article we wrote on that topic from the link below.
ValueError: A Step By Step Troubleshooting Guide!

But then ValueError does not describe where the problem is, is it in the length or the breadth?

If we wish to separate these 2 cases then we can raise 2 different exceptions as shown below.

def calcArea(length, breadth):
    if (length <= 0) or :
        raise InvalidLengthError
    elif (breadth <= 0):
        raise InvalidBreadthError
    else:
        return length*breadth

But then where did the InvalidLengthError and InvalidBreadthError come from? That is what we will learn next!

Note: Having 2 new exceptions in the example above is definitely an overkill, the example was just to get the point across on what kind of situations you might need to use custom exceptions!

How Do I Create A Custom Exception?

Creating a custom exception is fairly straightforward. To create a custom exception, you have to define a class that inherits from the built-in Exception class (or you can also inherit from one of the Exception class’s subclasses, for example, the ValueError class)

The syntax shown below can be used to create such a class

class MyCustomError(Exception):
    "This is my custom Exception class"
    pass

Let us next see an example of how this custom exception we just created can be invoked and caught. Also, let us see what output can we expect.

class MyCustomError(Exception):
    "This is my custom Exception class"
    pass

try:
    raise MyCustomError('Custom exception raised!')
except MyCustomError as e:
    print(e)
Custom exception raised!

In the above example, we’ve created a custom exception by creating a class that inherits from the Exception class.

Now we need to raise this exception to see if it’s working. To do that we can raise it inside the try block and catch the raised exception with a corresponding except block

try:
    raise MyCustomError('Custom exception raised!')
except MyCustomError as e:
    print(e)

If this is your first time seeing the raise keyword, I suggest reading the article in the link below to get up to speed!
Python: Manually throw/raise an Exception using the “raise” statement

Here, we’ve specified that an exception of the name MyCustomError is to be caught.

Note: The way this is processed is all the exceptions of the class MyCustomError and its subclasses (if any) will be caught. This means if you specified our MyCustomError’s superclass which is the Exception class, this code will still work in the same manner

except Exception as e:
    print(e)

If you wish to learn more about the usage try-except and related concepts, I suggest setting aside 10mins to read our comprehensive article on that topic below.
Python: “try-except-else-finally” Usage Explained!

Also, note how we suffixed the name of our custom exception with the word “Error“, this is a naming convention used in the Python developer community. This topic of “Custom Exception Creation” is covered in depth in our other article below.

Python: How to Create a Custom Exception

Alright so now that we have answered the questions

  • what is a custom exception? and
  • how to create one?

Let us move on to the next question on our list!

How to choose which exception to inherit from?

When choosing which exception to inherit from, it is important to remember that you must always inherit from the most relevant exception class. In other words, make sure that your custom exception is derived from the most appropriate class.

For example,

  • if your custom exception is about dealing with arithmetically related problems, it is best to use the ArithmeticError to inherit from.
  • if it’s about importing libraries you can inherit from ImportError or even its popular subclass ModuleNotFoundError.
  • If your problem is with the inputs given to a function or a method, then it’s best to inherit from ValueError
  • If your custom exception does not relate to any of these built-in exceptions, you can inherit directly from the Exception class itself.

This will ensure 2 things

  1. You will already get all the necessary attributes you need from the parent class and
  2. Your teammate reading your code will get more background info about your thought process behind your design.

If you are not already familiar with what in-built exception classes are available to inherit from, then I suggest you start your search by reading the article below.

7 Most Common In-Built Exceptions in Python!

Also have a look at the exceptions section of our website, where we have articles on most of the important inbuilt exceptions in Python.

One of the most important steps in becoming a professional Python craftsman involves acquiring knowledge of all the in-built exceptions available in the language. I assure you, an hour invested in learning this save you years of frustration later down the road!

Again invite you to read our article on custom exception creation for more insights and examples into how to choose an exception to inherit from!

How to choose between a custom exception vs an in-built exception?

This is another huge topic that deserves its article, hence we went ahead and did that for you, you can find several examples and all the necessary concepts you need to learn in that article (linked below)

Python: When To Use Custom Exceptions? Explained!

But the rule of thumb here is unless your situation specifically screams for a custom exception, then go with an in-built one!

The in-built ones can cover most of the use cases you will typically come across in your career, so use those and spare the users of your code from looking at unfamiliar/ non-googleable errors!

Next, let’s have some fun and look at some ideas on how to customize our class to get the most use out of it!

Ideas to customize your custom exception

We can customize our exception by editing our class’s attributes. If you recall, we create an exception by defining a class and making it inherit the Exception class. This means our exception now has all the attributes of the Exception class.

Now that we have these attributes from the superclass, we can simply edit them to suit our needs instead of writing them from scratch!

The “Exception” class is the one from which most built-in exception classes are built, you can explore what is inside that one by reading the article below.

“Exception” Class in Python: A Walk-through!

Next, let us look at how we can use the __str__() method to customize our Custom-exception class!

Customizing the __str__() method to add more clues in the error message

The __str__ method is called when you use the print() method on an object of any class in Python. It returns a string, which is usually the name of the class.

The __str__ function exists in all exception classes hence it is included in the Exception class too. When we create a custom exception, we inherit this __str__ function.

If you wish to, you can have your own __str__ function in your custom exception (which is nothing but the child class) by overriding the function in the Exception class (that’s the parent class).

Customization Example#1

For example, look at the two pieces of code below:

In this code, we override the parent class’s __str__() method:

class IncorrectScoreRangeError(Exception):
    def __init__(self, score):
        self.score = score

    def __str__(self):
        return f"IncorrectScoreRangeError: {self.score} is not in the valid (0, 100) range"

try:
    score = int(input("Enter your obtained score: "))
    if not 0 < score < 100:
        raise IncorrectScoreRangeError(score)
except IncorrectScoreRangeError as e:
    print(e)
Enter your obtained score: 101
IncorrectScoreRangeError: 101 is not in the valid (0, 100) range

And this code didn’t use the __str__() method:

class IncorrectScoreRangeError(Exception):
    pass
try:
    score = int(input("Enter your obtained score: "))
    if not 0 < score < 100:
        raise IncorrectScoreRangeError("The entered score is not in the valid (0, 100) range")
except IncorrectScoreRangeError as e:
    print(e)
Enter your obtained score: -5
The entered score is not in the valid (0, 100) range

Notice how the __str__() method helps in further customizing the output by printing the score by printing the actual value that led to the error!

Let’s have a look at another example just to gain more understanding!

Customization Example#2

Let’s say we need to develop a program that calculates the grade based on the user’s score. To do that, we need to

  • get the input score from the user
  • calculate the grade from the score

Now if the entered score is not in the range (1 to 100) we’ll simply raise our error.

Let’s start by creating the function that calculates the grades

def grade_calculator(score):
    if score >= 90: return "A"
    if score >= 80: return "B"
    if score >= 70: return "C"
    if score >= 60: return "D"
    return "F"
    
print(grade_calculator(78))

The function grade_calculator takes in a parameter score and returns a grade based on it.

Next, let’s define our custom exception for “score is out of range” scenario

class ScoreError(ValueError):
    min_score = 0
    max_score = 100

    def __init__(self, score, *args):
        super().__init__(args)
        self.score = score

    def __str__(self):
        return f'ScoreError: Your entered score {self.score} is not in the valid range required: {self.min_score, self.max_score}'

Here we have:

  1. Defined a custom exception class that inherits from the ValueError class
  2. Added two attributes to the class, min_score and max_score.
  3. Created a function called __init__ that takes in a parameter called score and any number of position arguments *args.
    • Use the super() keyword to call the __init__ method of the base class (which is the Exception class in this case)
    • Create the __str__ function to override the base class’s __str__ function so that you can print out your own custom message exception

Here is a quick side note on the super() method in Python if you are not familiar with the concept!

The super() method

This is nothing but a keyword used to access overridden parent class methods from the child class.

i.e. let’s say there was a function called my_func in the parent class, if you defined a function named my_func in the child class, this child class’s function would be used whenever called. The child class’s function has overridden the parent class function. The super() keyword allows you to access the parent class’s function even if it is overridden!

Example:

#creating a parent class with its method
class ParentClass:
    def my_method(self):
        print("I am a method from the parent class")

#creating a child class with its method
class ChildClass(ParentClass):
    #by defining using the same class name again, we have overridden the 'ParentClass' method with this 'ChildClass' method
    def my_method(self):
        print("I am a method from the child class")
        #the super keyword calls the 'ParentClass' method again
        super().my_method()
        
#creating a ChildClass object
child_obj = ChildClass()
#calling the 'my_method'
child_obj.my_method()
I am a method from the child class
I am a method from the parent class

The script which uses the above method and custom exception class is given below.

score = int(input("Please enter your score: "))
try:
  grade = grade_calculator(score)
  print(f'{score} score= {grade} grade')
except ScoreError as ex:
  print(ex)

How it works:

  1. Take the input from the user and store it in a variable called score
  2. Try to convert the variable score to an integer, if the user entered a number, the code works and moves to the next statement, otherwise, a ValueError is raised.
  3. In the else block,
    • We use another try block to call the grade_calculator function that we previously had defined by entering score as the parameter.
    • If the score happens to be out of range, ScoreError will be raised and handled in the except block
    • In the else block, the converted grade is converted and printed to the output screen

Putting it all together

def grade_calculator(score):
    if score < ScoreError.min_score or score> ScoreError.max_score:
        raise ScoreError(score)

    if score >= 90: return "A"
    if score >= 80: return "B"
    if score >= 70: return "C"
    if score >= 60: return "D"
    return "F"

class ScoreError(ValueError):
    min_score = 0
    max_score = 100

    def __init__(self, score, *args):
        super().__init__(args)
        self.score = score

    def __str__(self):
        return f'ScoreError: Your entered score {self.score} is not in the valid range required: {self.min_score, self.max_score}'



score = int(input("Please enter your score: "))
try:
  grade = grade_calculator(score)
  print(f'{score} score= {grade} grade')
except ScoreError as ex:
  print(ex)

When the score entered is in the expected range, no exception will be raised and we will get the following output.

Please enter your score: 65
65 score= D grade

If the user enters some absurd value, we will raise a ScoreError as shown below.

Please enter your score: 101
ScoreError: Your entered score 101 is not in the valid range required: (0, 100)

Again we got a similar result to the previous example, this time with even more information in the error message.

These are just 2 examples of customizing our new custom exception class. Your imagination is the limit for how much customization you can do!

For the next step in your Python journey I invite you to master some of the most common errors you will run into in your daily life as a Python developer. We have compiled a list of just 7 exceptions that you need to focus on if you wish to gain mastery of Exceptions in Python!

7 Most Common In-Built Exceptions in Python!

If you are a visual learner here is a YouTube video that we made on that same topic!

And with that being said, I will end this article.

Congratulations on making it to the end of the article, not many have the perseverance to do so!

I hope you enjoyed reading this article and found it useful!

Feel free to share it with your friends and colleagues!

If your thirst for knowledge has not been quenched yet, here are some related articles that might spark your interest!

Related Articles

Python: Details contained in an Exception

“Exception” Class in Python: A Walk-through!

Python: When To Use Custom Exceptions? Explained!

Python: How to Create a Custom Exception

Thanks to Namazi Jamal for his contributions in writing this article!

Photo of author
Editor
Balaji Gunasekaran
Balaji Gunasekaran is a Senior Software Engineer with a Master of Science degree in Mechatronics and a bachelor’s degree in Electrical and Electronics Engineering. He loves to write about tech and has written more than 300 articles. He has also published the book “Cracking the Embedded Software Engineering Interview”. You can follow him on LinkedIn