Python Exception Handling

In this section, we will explore how to handle errors and exceptions in Python using the try, except, else, and finally blocks. We'll cover how to catch exceptions, handle them effectively, and ensure that certain code runs regardless of whether an exception occurred.



Using try and except for Exception Handling

The try and except blocks are the most common way of handling exceptions in Python. The try block lets you test a block of code for errors, while the except block lets you handle the error gracefully without crashing the program. If an exception occurs in the try block, Python will stop executing the code inside the try block and jump to the corresponding except block.

Basic Syntax of try-except

The basic syntax for a try-except block is:

python
try:
    # Code that might raise an exception
    result = 10 / 0
except ZeroDivisionError as e:
    # Handling the exception
    print("Error:", e)

Output:

Error: division by zero

How It Works

In the example above, the code inside the try block attempts to divide by zero, which raises a ZeroDivisionError. The except block catches this exception and prints the error message.

Multiple except Blocks

You can also handle multiple types of exceptions in different except blocks. This allows you to handle different error types in a customized way.

python
try:
    x = int(input("Enter a number: "))
    y = 10 / x
except ValueError:
    print("Invalid input, please enter a number.")
except ZeroDivisionError:
    print("Error: Cannot divide by zero.")

Output (when input is '0'):

Error: Cannot divide by zero.

Catching All Exceptions

If you're unsure of the type of exception, you can catch all exceptions using a general except block without specifying the exception type.

python
try:
    x = 10 / 0
except Exception as e:
    print(f"An error occurred: {e}")

Output:

An error occurred: division by zero

Using finally for Cleanup

The finally block in Python is used to execute code that must run, no matter what. It is useful when you need to ensure that certain actions are always performed, such as closing files, releasing resources, or performing cleanup tasks, regardless of whether an exception occurred in the try block.

Syntax of finally

The finally block can be added after the try and except blocks, and it will always be executed after the code in the try block runs. Even if an exception is raised and caught, the finally block will still be executed.

python
try:
    file = open('sample.txt', 'r')
    content = file.read()
    print(content)
except FileNotFoundError as e:
    print("File not found:", e)
finally:
    file.close()  # Ensures the file is always closed

Explanation:

In the above example, the finally block ensures that the file is closed, even if an error occurs while reading the file (e.g., if the file is not found).

Example: Using finally to Ensure Cleanup

Here's another example where the finally block is used to ensure that a resource is always released, no matter what happens in the program.

python
def process_data():
    try:
        # Simulate an error (divide by zero)
        result = 10 / 0
    except ZeroDivisionError:
        print("Error: Cannot divide by zero.")
    finally:
        print("Cleanup: Closing resources and releasing memory.")
        
process_data()

Output:

Error: Cannot divide by zero.
Cleanup: Closing resources and releasing memory.

How It Works

Even though a ZeroDivisionError was raised inside the try block, the finally block is executed as part of the cleanup process, ensuring that the necessary actions are always performed regardless of any exceptions.

When to Use finally

Use finally when you need to guarantee that specific code is always executed after the try block, even if an exception occurs. This is often used for:

  • Closing files or database connections
  • Releasing system resources
  • Cleaning up temporary files or logs
  • Restoring program state after an error

Using else with try-except

The else block in Python is used in conjunction with try-except to specify code that should run only if no exception was raised in the try block. If an exception occurs, the code in the else block will be skipped, and the code in the except block will execute.

Syntax of else with try-except

The else block must be placed after the except block, and it will only run if no exception occurs within the try block.

python
try:
    num = int(input("Enter a number: "))
    result = 10 / num
except ValueError:
    print("Error: Invalid input. Please enter a valid number.")
except ZeroDivisionError:
    print("Error: Cannot divide by zero.")
else:
    print(f"The result is {result}.")

Explanation:

In this example, the else block will run only if there is no error in the try block. If the user enters a valid number and does not divide by zero, the result will be printed in the else block.

Example: Using else to Handle Successful Execution

Here's another example where the else block is used to perform actions after a successful operation, such as opening and reading a file.

python
try:
    file = open('data.txt', 'r')
    content = file.read()
except FileNotFoundError:
    print("Error: The file does not exist.")
else:
    print("File read successfully.")
    print(content)

Output (if the file exists):

File read successfully.
This is the content of the file.

In this case, if the file is successfully opened and read, the code in the else block is executed, printing a success message and the content of the file.

When to Use else with try-except

Use the else block when you want to run code that should only be executed if the try block runs successfully without errors. This allows for a clear separation of error handling and successful execution.

  • For code that should only run if the try block doesn't raise an exception.
  • To avoid nested code in the try block, keeping the successful case separate from error handling.
  • To improve code clarity and maintainability, as the successful execution is separated from the error handling logic.

Creating Custom Exceptions

In Python, you can create your own custom exceptions by defining a new class that inherits from the built-in Exception class or one of its subclasses. This is useful when you want to handle specific types of errors in a more descriptive and controlled way.

Syntax for Custom Exception

To create a custom exception, you simply define a new class and inherit from the Exception class. You can add additional attributes, methods, and messages to customize the behavior of your exception.

python
class MyCustomError(Exception):
    def __init__(self, message="Something went wrong!"):
        self.message = message
        super().__init__(self.message)}

In this example, the MyCustomError class is a custom exception that inherits from the base Exception class. The __init__ method allows you to pass a custom error message when raising the exception.

Raising a Custom Exception

After creating a custom exception, you can raise it using the raise keyword, just like you would with a built-in exception.

python
def divide(a, b):
    if b == 0:
        raise MyCustomError("Cannot divide by zero!")
    return a / b

try:
    result = divide(10, 0)
except MyCustomError as e:
    print(f"Error: {e}")

Output:

Error: Cannot divide by zero!

In this example, the custom exception MyCustomError is raised when attempting to divide by zero. The exception is then caught and handled in the except block, where the custom error message is displayed.

Adding Custom Attributes to Custom Exceptions

You can also add custom attributes to your exception class to provide more context about the error. This can be helpful when you need to include additional information when raising an exception.

python
class InvalidAgeError(Exception):
    def __init__(self, message="Invalid age provided", age=None):
        self.message = message
        self.age = age
        super().__init__(self.message)

def check_age(age):
    if age < 18:
        raise InvalidAgeError(age=age)
    print("Age is valid.")

try:
    check_age(15)
except InvalidAgeError as e:
    print(f"Error: {e.message} - Age: {e.age}")

Output:

Error: Invalid age provided - Age: 15

In this example, the custom exception InvalidAgeError includes an additional attribute age, which stores the invalid age that caused the exception. The error message and the age are then displayed in the except block.

When to Use Custom Exceptions

Custom exceptions are useful when:

  • You want to distinguish between different types of errors in your program.
  • You need to raise specific errors with custom messages that are more meaningful in your application.
  • You want to handle certain exceptions in a specific way that is different from the built-in exceptions.

Best Practices

Here are some best practices when creating custom exceptions:

  • Always inherit from the Exception class or one of its subclasses.
  • Provide a useful error message in the exception's __init__ method.
  • Consider adding custom attributes to provide more context about the error.
  • Ensure that your custom exception is descriptive enough to help with debugging.

Frequently Asked Questions

What is a try-except block in Python?

A try-except block is used to handle exceptions. Code that might cause an error goes in the try block. If an error occurs, it’s caught and handled in the except block.


What does the finally block do in Python?

The finally block always runs, whether or not an exception occurred. It’s ideal for tasks like closing files or cleaning up after operations.


When should I use the else block in try-except?

The else block runs only if no exception was raised in the try block. Use it for code that should only execute when the try is successful.


How can I create a custom exception in Python?

Define a class that inherits from Exception:
class MyError(Exception):
  pass


Should I catch all exceptions using 'except Exception'?

It's possible but not recommended. Catching all exceptions may hide bugs. It’s better to catch specific exceptions to handle known error cases clearly.



What's Next?

In the next section, we will explore advanced techniques for debugging and logging in Python. You'll learn how to use the built-in logging module to track errors and events in your application.