Python

How to Raise Exceptions in Python

In the Python programming language, raising exceptions has a crucial role in addressing errors and indicating unforeseen circumstances within the code. This is achieved using the “raise” keyword, followed by the specified exception type. For custom error handling, the developers can define new exception classes that are inherited from the pre-existing “Exception” class. This approach allows for creating personalized error messages and customized exception management. For graceful exception management, the “try”, “except”, and “finally” blocks are typically used. Within the “except” block, the “raise” statement is frequently employed to propagate or re-raise exceptions to ensure a systematic and robust error-handling strategy.

Example 1: Basics of Exception Handling

Exception handling in Python is fundamental to writing a robust and error-tolerant code. The ability to raise and catch the exceptions allows the developers to easily handle the unexpected situations and ensure the reliability of their programs. In this detailed guide, we’ll explore the complexities of raising exceptions in Python, covering various scenarios and providing detailed explanations for each example.

try:
    # Code that may raise an exception
    result = 10 / 0
except ZeroDivisionError as e:
    # Handling the exception
    print(f"Error: {e}")

Exception handling involves signaling and responding to exceptional conditions that may arise during program execution. The “try”, “except”, and “finally” blocks are the main building blocks to manage the exceptions. The code in this example that could throw a basic exception is located in the “try” block. We are trying to divide 10 by 0. However, this is failing and is throwing a “ZeroDivisionError”. This particular exception type is caught by the “except” block which enables us to treat it gracefully. It displays an error message in this instance.

Example 2: Custom Exception

While Python provides a range of built-in exception types, the developers can create custom exceptions by defining new classes that are inherited from the base “Exception” class.
As an illustration, we construct a custom exception called “CustomError” which is descended from the default “Exception” class. The “__init__” method is overridden to allow the addition of a custom error message when an instance of this exception is raised.

class CustomError(Exception):
    def __init__(self, message):
        super().__init__(message)

We create a function using custom exceptions. The “process_data” function takes the data as a parameter. If the provided data is empty like when it evaluates to False in a Boolean context, it raises an instance of “CustomError” with the “Data cannot be empty” message.

def process_data(data):
    if not data:
        raise CustomError("Data cannot be empty")

For exception handling, we set the data to “None” in the “try” block and call the “process_data” function. Since the data is “None”, the function raises a “CustomError”. The “except” block catches this specific exception (CustomError) and prints a custom error message, providing information about the nature of the error. Consider a scenario where the “process_data” function is part of a data validation process. If the provided data is essential for subsequent/later operations and an empty data that is set is considered an error, raising a “CustomError” helps to communicate and handle this specific condition clearly.

class CustomError(Exception):
    def __init__(self, message):
        super().__init__(message)

def process_data(data):
    if not data:
        raise CustomError("Data cannot be empty")

try:
    data = None
    process_data(data)
except CustomError as ce:
    print(f"Custom Error: {ce}")

In this example usage, the program prompts the user to enter the data, and the “process_data” function is called to validate the input. If the entered data is empty, a “CustomError” is raised, providing a clear and specific error message that can be caught and handled appropriately.

Custom exceptions enhance the code readability and maintainability by allowing the developers to define and communicate specific error conditions within their applications.

Example 3: Multiple Except Blocks

In this example, the “divide_numbers” function attempts to perform a division. It includes the separate “except” blocks for “ZeroDivisionError” and “TypeError”. The specific block is executed based on the type of exception raised. This method improves the readability of the code and enables an accurate error handling. Besides “try” and “except”, Python provides the “else” and “finally” blocks to further refine the exception handling.

def divide_numbers(a, b):
    try:
        result = a / b
    except ZeroDivisionError as zd:
        print(f"Error: Division by zero - {zd}")
    except TypeError as te:
        print(f"Error: Invalid data type - {te}")

# Example usages
divide_numbers(10, 2)       # No exception
divide_numbers(10, 0)       # ZeroDivisionError
divide_numbers(10, '2')     # TypeError

Example 4: Else and Finally Blocks

In this example, the “open_file” function attempts to open a file for reading. The “except” block handles the “FileNotFoundError”, the “else” block executes if no exception occurs, and the “finally” block always runs, providing a system for cleanup or finalization.

file_path='/content/sample_data/README.md'
def open_file(file_path):
    try:
        file = open(file_path, 'r')
    except FileNotFoundError as fnf:
        print(f"Error: File not found - {fnf}")
    else:
        # Code in the else block runs if no exception occurs
        print(f"File contents: {file.read()}")
        file.close()
    finally:
        # Code in the finally block always runs, regardless of exceptions
        print("Execution complete, whether an exception occurred or not")

# Example usage
open_file("example.txt")

Example 5: Raising Exceptions with a Cause

In Python, exceptions can be raised with an optional “from” clause, specifying another exception as the cause. Let’s dive deeper into this and demonstrate raising exceptions with a cause in Python. We need first to create a function to read the files. The “read_file” function takes a “file_path” as a parameter and attempts to open and read the file’s content. If the word “error” is found in the content, it raises a “ValueError” with the “Invalid content found” message and “None” as the cause.

file_path='/content/sample_data/README.md'
def read_file(file_path):
    try:
        with open(file_path, 'r') as file:
            content = file.read()
            if 'error' in content:
                raise ValueError("Invalid content found") from None
    except ValueError as ve:
        print(f"Error reading file: {ve}")

In the previous code, the “raise” statement is used to raise a “ValueError” with the specified message. The “from None” part indicates no explicit cause for this exception. In Python 3.3 and later versions, this “from” clause allows associating another exception (the cause) with the current exception.

raise ValueError("Invalid content found") from None

The “except” block catches the “ValueError” that is raised in the “try” block and prints an error message. The “ve” variable holds the instance of the “ValueError” including its message and cause.

except ValueError as ve:
    print(f"Error reading file: {ve}")

Consider a situation where a file is processed and the presence of a certain content is considered an error. Using the “raise … from …” syntax, the code indicates that the primary cause of the error is the file’s content. This information can be valuable when debugging, providing a clear chain of events that leads to the exception.

# Example Usage
try:
    read_file("example.txt")
except ValueError as ve:
    print(f"Error during file processing: {ve}")

In this example, the program attempts to read the content of a file. If the word “error” is found, a “ValueError” is raised with the specified message. The “except” block catches and handles this exception, allowing the program to respond appropriately to the identified issue.

By associating a cause with an exception, the developers can create more informative and traceable error messages to help identify and resolve the issues within their code. This feature is handy in scenarios where understanding the context of an exception is essential for effective debugging and problem-solving.

Conclusion

Python programming’s exception handling feature is crucial since it offers a way to manage the errors and unforeseen circumstances easily. By utilizing the “try”, “except”, “else”, and “finally” blocks, the developers can create strong and fast applications that are more resilient to various error scenarios. Custom exceptions further enhance the ability to convey a specific error information, making the code more readable and maintainable.

About the author

Omar Farooq

Hello Readers, I am Omar and I have been writing technical articles from last decade. You can check out my writing pieces.