How I Debug my Python Projects — Part 1: Raising Exceptions

eyong kevin
ITNEXT
Published in
7 min readMar 16, 2020

--

I’ll start with this quote by Edsger Dijkstra

If debugging is the process of removing software bugs, then programming must be the process of putting them in

Throughout my career, I have worked on many python projects; big, small and medium sized projects. It wasn’t easy at first since I believed that a good programmer should be able to write bug-free code. However, I later understood that the best way to learn is to make mistakes, try, dare and learn from the outcome. I will stop here with this quote by Nicholas Negroponte

Programming allows you to think about thinking, and while debugging you learn learning

Below are the techniques I use in debugging my python projects:

  • Raising Exceptions
  • Assertions
  • Python Logging Module
  • Python Debugger Module
  • Python Jupyter Notebook

In this article, I will walk you through the first technique — Raising Exceptions

Raising Exceptions

Exceptions are raised when the program encounters an error during its execution. They disrupt the normal flow of the program and usually end it abruptly. Exceptions are raised with the raise statement. In code, a raise statement consists of the following:

  • The raise keyword
  • A call to the Exception() function
  • A string with a helpful error message passed to the Exception() function.

For example, if you enter the following code into your interactive shell

raise Exception(“The error message goes here”)

you will get the following traceback:

Traceback (most recent call last):
File "<stdin>", line 1, in <module>
Exception: The error message goes here

The traceback includes the error message, the line number of the line that caused the error, and the sequence of the function calls that led to the error. This sequence of calls is called the call stack.

If there is no try and except and an optional else covering the raise statement that raised the exception, the program simply crashes and displays the exceptions error message.

How I Use Exceptions to debug my code

So how do I use exceptions to debug my code? Very good questions. The idea is that I check my code and detect any potential errors that may occur in the future and raise those errors earlier. This way, I can handle the errors before there cause a lot of damage and become difficult to find.

I will explain base on the common exception errors that I encounter while coding.

It is worth noting that, exceptions are mostly considered as user errors. For example, whenever a user inputs an incorrect data or a computation fails because of wrong data, an exception should be generally raised. In most cases, these exceptions are handled and the user is prompted to input the right value. But in other cases, there are not handled and the program is allowed to crash

IndexError

raised when you try to index a list, tuple, or string beyond the permitted boundaries

If in my program, I know I will be dealing with indexing a list, and say the computation that will take place before indexing the list is complex and will take a good amount of time. I won’t want to wait till this computation takes place before receiving the IndexError exception if it occurs. what I do is I make sure the index I am about to use is in the range. Let’s consider the following code

def compute_get_index(index, arr):
# Do some complex computations on the array
# that may take a lot of time
return arr[index]
compute_get_index(8, [3,2,9])

If I run the above function, the indexError exception will be raised by the program because the index 8 is out of range. The disturbing part of it is that, the complex computation will first execute before the error is raised. So, to detect this bug early, I will check to make sure the index is in range. If not, I raise an exception with a nice message. This way, we get the error and the boring complex computation doesn’t execute

KeyError

raised when you try to access the value of a key that doesn’t exist in a dictionary.

The same principle applies as in IndexError above. Say you know you will be accessing a dictionary by key, and you are uncertain if the key gotten from either user input or computation is actually in the dictionary. The way I handle this is I first check to make sure it is a key in the dictionary. If it is not, I raise a KeyError exception early and handle it rather than allowing my code to run just for it to fail with the same error at the end

AttributeError

raised when an invalid attribute reference is made, or when an attribute assignment fails

This is also a very common case. At times, I will call the `append` method from a None object. Or a `get` method from a list. This happens because at times what I may be expecting as output from a function or process is not actually it. To debug this, I make sure I check if the object in question has that attribute. If it doesn’t, I raise the AttributeError exception early, handle it and move on.

From the code above, It shows that I am expecting my object d to have the attribute append . For the second case, I am expecting my object d to be a list before I carry out my complex computation.

ValueError

Raised when a function receives an argument of the correct type but an inappropriate value

This exception is raised in a lot of mathematical operations. For example, in the math module method math.sqrt()(square root of a negative number raises a ValueError exception). If a function works for only a specific range of values, it is reasonable to raise a ValueError if the argument received doesn’t meet the specific range.

So, if I know my function works only with a range of values, I will make sure to check the value before commencing the long boring complex operation, so that I catch it early, deal with it and move on.

Take for example the function below. I know that my complex operation works only with positive values. So what I do is check to make sure the value that is to be used in the operation is positive. If not, I raise the exception, spare myself from running the complex operation that will obviously fail, fix the issue and move on.

How I build my Custom Exception Classes

We have many other cases which we can handle during debugging like:

  • ZeroDivisionError raised when you try to divide by zero
  • IOError raised when an I/O operation fails for an I/O-related reason
  • FileExistsError raised when trying to create a file or directory which already exists.
  • IsADirectoryError raised when a file operation is requested on a directory
  • NotADirectoryError raised when a directory operation is requested on something which is not a directory.

I use all of these to make sure my code works as intended before moving into the more complex part of my code.

In many cases, you may not find a build-in exception that matches the kind of restrain you want to apply in your code. In this case, you can create a custom exception that meets your needs.

Say you want to check and make sure a particular value is greater than 10. Of cause you can use the ValueError but what if you want to customize the error the more by giving it a specific and unique name like ValueSmallerThanTenError. This is how I create my custom exception class

When creating a module that can raise several distinct errors, a common practice is to create a base class for exceptions defined by that module, and subclass that to create specific exception classes for different error conditions

class MyException(Exception):
"""Base class exceptions for this module"""
def __init__(self, msg):
self.msg = msg
def __str__(self):
return "{}".format(self.msg)

My base exception has the __init__() method that takes in the message that is been raised. It also has the __str__() method because when raising an exception, we are creating an exception instance and printing it at the same time.

  • Then, I create my different exception classes that inherit the base class to handle specific errors.
class ValueSmallerThanTenError(MyException):
def __init__(self, msg="ValueSmallerThanTenError occured"):
super().__init__(msg)

Let’s test our custom exception class

Conclusion

These are the ways I use exceptions to debug my code. It may be the wrong way or maybe someone out there has a better way to use Exceptions to debug a python project. Please share your thoughts and idea by commenting.

In my next article, I will show you how I use assertion to debug my code. I hope you enjoyed reading my article and found it helpful.

--

--

Bachelor degree in software engineering, currently working on Machine Learning, AI, Deep Learning and React. When I am not working, I produce beats.