The world’s leading publication for data science, AI, and ML professionals.

One Line of Code to Say Goodbye to Confusing Python Error Messages

Flexible error message enhancement library – PrettyError

Image by 15548337 from Pixabay
Image by 15548337 from Pixabay

Programming is such an activity that we may use 20% of our time to write the ideas into code and then 80% to clear the errors and fix the bugs. Error messages are definitely something we will see every day. However, have you experienced any difficulties with Python Error messages?

For example, the error messages can be very verbose, which is not bad, but it is difficult to distinguish different parts and quickly find the information we need. The stack traces are also sometimes too overwhelming and complex to understand. It is also not easy to customise the error message unless we override the Exception classes, which could be overwhelming again.

In this article, I’m going to introduce a library called PrettyError that can help us address all of the pain points mentioned above and more. It has many cool features that can streamline our debugging process and help us save lots of time during the coding jobs.

1. Installation & Quick Start

Image by Pexels from Pixabay
Image by Pexels from Pixabay

As usual, installing the Pretty Error library is quite easy. We just simply run pip to obtain it from PyPI.

pip install pretty_errors

A Quick Start

This is probably the quickest quick start guide. When we want to use the library with its default configuration, all we need to do is to import it before coding.

import pretty_errors

Now, let’s define a function without try-except so that later on we can manually create some errors.

def divide(a, b):
    return a / b

Then, let’s first see what it looks like without Pretty Errors. We will simulate a division by zero error.

divide(1, 0)

That is the original error message in Python. Then, let’s enable Pretty Error by simply importing it, and running the code again.

import pretty_errors

divide(1, 0)

It’s already colour-coded and comes with simplified indicators such as the stdin and the function name divide.

Because of the limitation of this blog article, it’s not practical to demo how the colour code will be useful in practice. There is a screenshot from the GitHub README, as shown below.

Image Courtesy: https://raw.githubusercontent.com/onelivesleft/PrettyErrors/master/example.png
Image Courtesy: https://raw.githubusercontent.com/onelivesleft/PrettyErrors/master/example.png

In the above scenario, do you like the one on the left (original) or on the right (Pretty Error)? 🙂

2. Key Features

Image by Robert Owen-Wahl from Pixabay
Image by Robert Owen-Wahl from Pixabay

Of course, the library can do more than what we showed in the quick start. In this section, I’ll pick up some key features for demonstration.

2.1 Configuration of Color Code

The colour code really makes the error message much more readable, not surprised, PrettyError allows us to customise the color code.

For example, let’s put the following code into a Python Script file app.py.

import pretty_errors

open("non_existent_file.txt")

Then, let’s run this script to see what happens.

$ python app.py

OK, above is the default colour code in PrettyError. What if we want to customise the code? That is easy, just use the configure() function in the pretty_error module as below.

import pretty_errors

pretty_errors.configure(
    line_color = pretty_errors.BRIGHT_RED,
    exception_color = pretty_errors.BRIGHT_MAGENTA,
    exception_arg_color = pretty_errors.CYAN,
    exception_file_color = pretty_errors.RED_BACKGROUND + pretty_errors.BRIGHT_WHITE
)

open("non_existent_file.txt")

Let’s run this script file again.

There are 16 different error message output types such as the function_color, code_color and the syntax_error_color and PrettyError can customise them all.

Also, you may notice that I have used the enumeration provided by PrettyError for the default colours. There are 9 different main colours including BLACK, GREY,RED,GREEN,YELLOW,BLUE,MAGENTA,CYAN and WHITE. Every colour also comes with prefix BRIGHT_ and suffix _BACKGROUND, so there is a range of different default colours for us to use.

Apart from that, you may notice that I have used combined colours in the above configuration code.

pretty_errors.RED_BACKGROUND + pretty_errors.BRIGHT_WHITE

This is also allowed, so the background and font colours can be used together for a particular message type. It can be seen that PrettyError provides enough colour code options if we want to customise the error messages.

2.2 Adding Timestamps

One of the drawbacks of the original Python error messages is that it doesn’t display the timestamps along with the errors. Of course, the common solution is to add the Logging module to the program.

However, if the program crashes with completely uncaught and unknown exceptions, the logging solution usually won’t happen either. Also, sometimes we may just want to use some lightweight solution for some reason.

Of course, PrettyError can do that by simply adding the following configuration.

import pretty_errors

pretty_errors.configure(
    display_timestamp=1
)

open("non_existent_file.txt")

The configuration display_timestamp will display a timestamp along with the error as follows.

But wait, the timestamp is perf_counter that isn’t quite human-readable. Don’t worry, there is another configuration that can help us to customise the timestamp function. Below is an example to use the datetime module to get the current timestamp. Since the configuration takes a function as an argument, the easiest way is to pass a lambda function.

import pretty_errors
import datetime

pretty_errors.configure(
    display_timestamp=1,
    timestamp_function=lambda: datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
)

open("non_existent_file.txt")

Of course, if you want a very customised time format, it would be a good idea to define the function before the configuration, and then pass the function name only.

2.3 Show More Code in an Error

Another limitation of the original Python Error message is that it only displays the line of code with the error.

Sometimes, it may not be enough because the other code before the code that the error happens at may be the key factor of the error. Apart from that, it may also be useful to show the code after it so that we know if the error didn’t happen what will be executed.

Yes, this is doable in PrettyError using the configuration lines_before and lines_after.

import pretty_errors

pretty_errors.configure(
    lines_before=2,
    lines_after=1
)

# The output shouldn't show this comment (lines before)
# The output should show this comment (lines before)
def calculate(x):
    return 1 / x
# The output should show this comment (lines after)
# The output shouldn't show this comment (lines after)

def wrapper():
    calculate(0)

wrapper()

Please focus on the comments in the above code snippet. Based on the lines before and after configuration, I’ve put some comments to show up to which line of code should be displayed in the error message.

Also, see that tracing stack? we can control the level of tracing stacks too. This feature would be very useful sometimes when we don’t care what is the stack up to the main method. So, we can simplify the error message to help us focus on where the error happens exactly.

This can be achieved by the configuration item stack_depth.

import pretty_errors

pretty_errors.configure(
    stack_depth=1
)

def calculate(x):
    return 1 / x

def wrapper():
    calculate(0)

wrapper()

So, there are fewer stack depths now.

2.4 Show Variable Values

I bet this is one of the most desired error messages in Python but missing in the original one. When we are dealing with most of the bugs during development, it is very common that we have to use debugging tools to try to reproduce the issue.

What if the error message can tell you what are the values of the variables? Perhaps in the majority of the scenarios, we can troubleshoot the issues straight away!

Enabling this feature in PrettyError is also very simple, we can set the configuration item display_locals to 1.

import pretty_errors

pretty_errors.configure(
    display_locals=1  # Enable the display of local variables
)

def calculate_divide(x, y):
    return x / y

calculate_divide(1, 0)

Let’s run this script and have a look.

The error message told us it was a ZeroDivisionError. If we check the output of the variables x and y, we can see that the value of y is 0. Therefore, the debugging efficiency will be even higher with this feature enabled.

3. Best Practice with Environments

Image by YeriLee from Pixabay
Image by YeriLee from Pixabay

In practice, we may want the error messages to be very verbose sometimes, but not always. In terms of the features introduced above, the preference for enabling or disabling them can vary in different environments, too.

Therefore, it is suggested that we should leverage environmental variables to control the behaviours of PrettyError. Here is an example.

import pretty_errors
import os

# Configure PrettyErrors based on the environment
if os.getenv('ENV') == 'development':
    pretty_errors.configure(
        stack_depth=0,  # Show full stack
        display_locals=1  # Show local variables in development
    )
else:
    pretty_errors.configure(
        stack_depth=1,  # Show only 3 levels depth
        display_locals=0  # Hide local variables in production
    )

# Main Program
def calculate(x):
    return 1 / x

def wrapper():
    calculate(0)

wrapper()

Let’s run the scripts with different environment variables. The results are shown below.

Please be advised that you should use set ENV=development if you are using Windows OS.

Of course, you may have whatever else customisation and configurations for different environments using this approach.

Summary

Image by Hans from Pixabay
Image by Hans from Pixabay

In this article, I have introduced PrettyError. It is designed to fix some limitations existing in the Python error messages to make sure the error messages are more understandable, and consequently improve the development and debugging efficiency.

There are many useful features such as colour coding, timestamp inclusion, variable values displaying and customisable stack traces. Of course, as a debugging tool, we may need to think about whether we want it to work in all environments. So, the environmental variables can be introduced to solve the problem.

If you are interested in this library, you can check it out here.

GitHub – onelivesleft/PrettyErrors: Prettify Python exception output to make it legible.

Unless otherwise noted all images are by the author


Related Articles