DEV Community

Cover image for Error Handling in Python: Best Practices. Explore how to handle exceptions effectively
Augustine Alul
Augustine Alul

Posted on

Error Handling in Python: Best Practices. Explore how to handle exceptions effectively

Excerpt:
Errors are non-recoverable; when our program meets an error it quits or crashes instantly.

A good programmer ensures that their code or piece of software is written to gracefully handle errors/exceptions that may arise from the usage of the software without crashing or producing bad results. Imagine writing software for a financial institution that accepts numbers only from the user and an alphabet was entered instead of a number for arithmetic operation, this would typically raise an error, and the software would crash just because of one user if there is no proper mechanism to handle the error. This is not a good thing at all—it is bad for business, can lead to customers feeling frustrated and someone might end up losing their job for incompetence.

In this article, we’ll learn how to handle errors that may arise in our code due to user interaction in the best possible way. Stay tuned.

Prerequisites

This article is suitable for:

  • Python software developers seeking to learn how to handle exceptions in their code.

  • Persons already conversant with Python who wish to learn about the concept of error handling in Python.

  • Existing professionals who seek to sharpen existing knowledge on error handling in Python.

Objectives

By going through this article, the reader should be able to:

  • Clearly understood the concept of error handling in Python and why it is important.
  • Learn about custom exception classes and how to implement them.
  • Understand the key differences between errors and exceptions in Python.

Explaining Errors and Exceptions

Errors and exceptions are oftentimes used interchangeably, but they technically mean different things. In Python both errors and exceptions are subclasses of BaseException, this further shows that they have similarities in common even though they are different.

Errors

Errors are non-recoverable; when our program meets an error it quits or crashes instantly. There is no room for handling errors programmatically even if you might expect them. Some of the errors are listed below:

SyntaxError:

This is one of the most common types of errors programmers face, it results when a code is not following the correct Python syntax and can be detected during parsing. It Is easily an issue for learners of people switching from another programming language to Python.

name = “Austin”;
print(name)

Enter fullscreen mode Exit fullscreen mode

This results in a SyntaxError because, in Python, statements do not end with a semicolon.

IndentationError:

This error occurs when Python code is not properly indented and is detected when the code is being parsed. Indentation in Python is very important. It is the only way code can be defined in blocks in Python, unlike most languages where curly braces are used.

active = True
if (active):
print(“The user is active”)

Enter fullscreen mode Exit fullscreen mode

This code will result in an error be my cause of improper indentation. It should be:

if (active):
    print(“The user is active”)
Enter fullscreen mode Exit fullscreen mode

Exceptions

Exceptions in Python occur at runtime. Unlike Errors, they can be handled or caught properly programmatically for our program to continue running without crashing. In other words, they are recoverable. Here are some of the common exceptions found in Python:

Built-in Exceptions

These types of exceptions are part of the Python programming language. Listed below are a few of them:

ValueError:

This occurs when an invalid parameter is given to a function even though the type may be correct.


def str_num(num_string):
    return(int(string))

Enter fullscreen mode Exit fullscreen mode

From the code snippet above, if we pass in a numeric string to the function, it will be successfully converted to a number, otherwise, it will yield a ValueError.


print(str_num(“123”)) #works perfectly
print(str_num(“abc”)) #raises a ValueError 

Enter fullscreen mode Exit fullscreen mode

TypeError:

This is raised when the argument(s) of inappropriate type is passed to the function.

def addition(num1, num2):
    return num1 + num2
# calling the function
addition(5, A)

Enter fullscreen mode Exit fullscreen mode

This raises a TypeError.

IndexError:

This arises when you are trying to access a value in a list with a wrong index.

my_list = [“dog”, “cat”]
my_list[2]

Enter fullscreen mode Exit fullscreen mode

This results in an IndexError because my_list[2] is not accessible.

KeyError:

This is raised when trying to access a value in a dictionary using the wrong or inexistent key.

my_dict = {“name”: “Austin”, “occupation”: “Software Engineer”}
my_dict[“age”]

Enter fullscreen mode Exit fullscreen mode

This raises a KeyError.
You can find other built-in exceptions here.

Custom Exceptions

Custom exceptions are defined by the programmer. Here, Python makes it possible to manually define conditions that a program should check for during execution and raise an exception if found. You can achieve this by creating a class that inherits from the Exception class.

Handling Exceptions

Handling exceptions ensures that our software application has predictable performance when encountered by certain errors that arise during the application lifecycle. In this section, you will learn how this is done programmatically.

Using the try-except statements

The try-except statements provide a safe way of handling codes that are likely going to raise an error or exception. The try statement wraps the problematic code or try clause; this is the part of the code that is mostly going to bring about an error to the entire program; this is likely to happen when receiving inputs from users, reading from files, to name a few.

The except statement marks the beginning of the except clause; Which is the remaining code wrapped in the except block. The except clause handles the exception raised in the try block.

Let me walk you through the execution workflow. Your Python program typically executes until it reaches the try block where your “problematic” code is likely going to be, if there is no possible error while executing the code in the try block at the time, it skips the except block and continues to execute the rest part of the codebase. But if an error is encountered while executing the code in the try block, an exception object is created after which the control jumps immediately to the except block where it should be handled by the matching exception class.

try:
    age = int(input(“enter your age”))
    print(f“you were born {age} years ago”)

except ValueError:
    print(“ValueError was raised, please enter numbers only”)

Enter fullscreen mode Exit fullscreen mode

From the code snippet above, if a non-numeric value is passed to the program, an exception object is created and a ValueError is raised. The control immediately jumps to the except block where it scans for the appropriate exception class. Here, the ValueError class is sufficient. The error is handled properly. But if the proper class is not found, the control moves to the outer try block if there is any, if the exception is not properly handled still, the program crashes.

Using multiple exception classes with one except statement

Multiple exception classes can be checked for and the specific exception handled. This approach is preferred if you are not sure which exception out of a few, will result from the execution of your code. See the code snippet below.

except (RuntimeError, TypeError, NameError):
    pass

Enter fullscreen mode Exit fullscreen mode

Wildcard Exception class

The Exception class is a direct subclass of the BaseException. The Exception class is the base class of all the non-fatal exceptions.
Most of the time, the Exception class is sufficient for handling most of the exceptions raised during code execution.

try:
    # Code that may raise an exception
except Exception:
    # Handle the exception

Enter fullscreen mode Exit fullscreen mode

Even though the Exception class is capable of handling on-fatal exceptions, it is best to use it sparingly. Use the right Exception class, as it is more beneficial for debugging and making your code readable.

Using the finally statement

The piece of code wrapped inside the finally block executes whether or not an exception occurs. This makes it suitable for handling clean-up tasks; closing files, and releasing memory resources.


try:
    # Code that might raise an exception
except SomeException:
    # Handle the exception
else:
    # Optional: Executes if no exceptions occur
finally:
    # Code that always executes, no matter what

Enter fullscreen mode Exit fullscreen mode

Creating custom exception classes

Creating custom exceptions gives the programmer the ability to come up with specific exceptions for a software program. These can entail special conditions or edge cases that could be detrimental to the functioning of the particular software program in question. The custom exception classes defined must inherit from the Exception class.

class InvalidAgeError(Exception):
    pass

def check_age(age):
    if age < 0:
        raise InvalidAgeError("Age cannot be negative.")

try:
    check_age(-5)
except InvalidAgeError as e:
    print(f"Error: {e}")

Enter fullscreen mode Exit fullscreen mode

The code snippet above shows how custom exceptions are created and used. It can be used in a more complex way, depending on the use case.

Why error/exception handling is important

“Don't trust the user” is a common saying amongst software developers, this reiterates that you cannot fully determine how the users will interact with your software application; what type of inputs they pass in, and some other edge cases that you as a programmer might not have thought of while writing the application. Explained below are some of the reasons why it is important to handle errors/exceptions properly:

  1. Prevents Crashes Without exception handling, an unhandled error can cause your program to crash abruptly. This can lead to data loss or a poor user experience. Example: Without Exception Handling:
print(10 / 0)  # ZeroDivisionError: division by zero

Enter fullscreen mode Exit fullscreen mode

With Exception Handling:

try:
    print(10 / 0)
except ZeroDivisionError:
    print("Cannot divide by zero!")

Enter fullscreen mode Exit fullscreen mode
  1. Improves User Experience Properly handled exceptions provide meaningful error messages instead of cryptic system errors, keeping the application user-friendly. Example:
try:
    age = int(input("Enter your age: "))
except ValueError:
    print("Invalid input! Please enter a number.")

Enter fullscreen mode Exit fullscreen mode
  1. Maintains Application Stability It allows the application to continue running even after encountering an error, ensuring stability. Example:
def divide(a, b):
    try:
        return a / b
    except ZeroDivisionError:
        return "Division by zero is not allowed!"

print(divide(10, 2))  # Output: 5.0
print(divide(10, 0))  # Output: Division by zero is not allowed!

Enter fullscreen mode Exit fullscreen mode
  1. Handles Edge Cases Exception handling lets you account for unpredictable scenarios, such as network failures, missing files, or invalid user input. Example:
try:
    with open("data.txt", "r") as file:
        content = file.read()
except FileNotFoundError:
    print("The file was not found.")

Enter fullscreen mode Exit fullscreen mode
  1. Encourages Clean Code
    By separating normal logic from error-handling logic, exception handling makes your code easier to read, debug, and maintain.

  2. Facilitates Debugging
    With detailed error messages and exception logging, developers can quickly identify and fix issues in the code.
    Example:

import logging

try:
    result = 10 / 0
except Exception as e:
    logging.error("An error occurred", exc_info=True)

Enter fullscreen mode Exit fullscreen mode
  1. Essential for Critical Systems In systems where reliability is vital (e.g., banking, healthcare), exception handling is necessary to ensure errors are managed safely without data corruption or loss.

Conclusion

Errors and exceptions are most times used interchangeably in programming speak. The key difference between errors and exceptions in Python is in how they affect our software programs. Errors like syntax errors, and indentation errors crash our program when it is being parsed by the interpreter. Exceptions on the other hand crash our programs during runtime if not handled properly. Custom exceptions extend error-handling capabilities by making it possible for the programmer to define exception classes that are peculiar to a particular software application.

Error handling is also very important for debugging. It makes it possible to see why errors are occurring in our application, arming the software maintainer with enough information to fix the problem. Always ensure to introduce exception handling in your code appropriately to ensure a robust software application.

Thanks for reading.

Top comments (0)