11. Errors and Exceptions#

In Python, errors, also known as exceptions, are events that disrupt the normal flow of a program’s execution. Understanding how to manage these errors is fundamental for writing robust, maintainable, and error-resistant code. In this section, we will explore the key concepts related to error handling in Python, which will enable you to write programs that can gracefully handle unexpected situations without crashing.

11.1. Errors and Exceptions#

Errors in Python occur when something goes wrong during the execution of a program. These can generally be categorized into two main types:

Syntax Errors: These errors are detected by the Python interpreter before the code is executed, during the parsing stage. Syntax errors occur when the code violates the grammatical rules of Python. For instance:

print("Hello, world"

The above code will raise a SyntaxError because of the missing closing parenthesis. Since syntax errors prevent the program from running, they must be corrected before the code can be executed.

Exceptions: Unlike syntax errors, exceptions (see Exception) occur during the execution of the program. These are runtime errors that disrupt the normal flow of a program. Common examples include attempting to divide a number by zero, accessing an index that is out of range in a list, or trying to open a file that does not exist. For example:

x = 10 / 0  # Raises a ZeroDivisionError

Here, a ZeroDivisionError is raised because division by zero is mathematically undefined and is thus not allowed in Python.

11.2. Try and Except (Handling Exceptions)#

Python provides a mechanism to handle exceptions and prevent the program from crashing abruptly using the try and except blocks. This allows you to “try” a block of code and “catch” exceptions that may occur, enabling the program to continue running or to exit gracefully with an informative message.

  • Try Block: The code that might raise an exception is enclosed within a try block.

  • Except Block: If an exception occurs within the try block, the corresponding except block is executed to handle the error.

Example:

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

In this example, instead of the program crashing with a ZeroDivisionError, the except block catches the error and prints a user-friendly message.

11.3. Catching Specific Exceptions#

In Python, it’s possible to catch specific exceptions to handle different types of errors in various ways. This approach enhances the flexibility and clarity of your code, allowing you to address specific issues individually.

try:
    num = int(input("Enter a number: "))
    result = 10 / num
except ZeroDivisionError:
    print("You can't divide by zero!")
except ValueError:
    print("Please enter a valid integer.")

In this code, if the user enters a non-integer, a ValueError exception is caught, and a message is printed. If the user enters zero, a ZeroDivisionError is caught. This allows the program to handle different types of errors with specific responses.

11.4. Else and Finally Blocks#

In addition to try and except, Python also provides a else block. It executes only if no exceptions were raised in the try block. This is useful for placing code that should only run when the try block completes successfully without any errors.

try:
    result = 10 / 2
except ZeroDivisionError:
    print("Division by zero!")
else:
    print("Division successful. Result:", result)

the finally block, which can be used to define actions that should be executed no matter what—whether an exception occurs or not. This is particularly useful for cleanup tasks, such as closing a file or releasing resources.

file = None
try:
    file = open('example.txt', 'r')
    # Perform file operations
finally:
    if file is not None:    # A more pythonic test is simply "if file:"
        file.close()        # This will execute no matter what

In this example the finally block ensures that the file is always properly closed, preventing potential resource leaks. Warning file is initialized only if the open method do not raise exception. Thus in the finally block we have to test if it is not None. We will discute file management in more details.

11.5. Raising Exceptions (Using the raise Keyword)#

Note

We haven’t covered Python’s object-oriented approach yet, so we don’t know what a class is. For now, we will simply consider a class as a new type. We will use the above syntax in an idiomatic way.

In some cases, you might want to intentionally raise an exception to signal that an error has occurred or to enforce certain conditions within your code. This can be done using the raise keyword.

def divide(x, y):
    if y == 0:
        raise ValueError("Cannot divide by zero!")
    return x / y

try:
    divide(10, 0)
except ValueError as e:
    print(e)

Here, the divide function raises a ValueError if the divisor is zero, ensuring that the division operation adheres to valid mathematical rules.

11.6. Custom Exceptions#

Python allows you to define your own custom exceptions by creating a new exception class that inherits from the built-in Exception class. For now, we will use this as an idiomatic, but we will soon introduce the class (see Class). Custom exceptions are useful for handling specific scenarios that are unique to your application’s logic.

class NegativeNumberError(Exception):
    pass

def check_positive(num):
    if num < 0:
        raise NegativeNumberError("Negative numbers are not allowed.")

try:
    check_positive(-5)
except NegativeNumberError as e:
    print(e)

In this example, a custom exception NegativeNumberError is defined and raised when a negative number is encountered. This provides a clear and specific error message, improving the readability and maintainability of the code.