Python Exception Handling

PythonBeginner
Practice Now

Introduction

In computing and computer programming, exception handling is the process of responding to the occurrence of exceptions – anomalous or exceptional conditions requiring special processing. Python has many built-in exceptions that are raised when a program encounters an error. Additionally, many external libraries include their own custom exceptions that you will need to handle.

In this lab, you will learn the fundamentals of exception handling in Python. You will start with basic try and except blocks, move on to handling multiple exceptions, understand the use of the finally block, and finally, create and manage your own custom exceptions.

Basic Exception Handling

In this step, you will learn the fundamentals of exception handling in Python using try and except blocks.

It's a mathematical fact that you can't divide by zero. If you attempt this in Python, the interpreter will raise a built-in ZeroDivisionError. Let's see this in action.

Note: If you are not familiar with using WebIDE (VS Code like), please learn it first at this lab.

First, open the file basic_handler.py from the file explorer on the left and add the following code. This defines a simple division function.

def divide(dividend , divisor):
    print(dividend / divisor)

divide(dividend=10, divisor=5)

Now, run the script from the terminal:

python3 basic_handler.py

You will see the correct output:

2.0

Next, let's see what happens when we try to divide by zero. Modify the last line in basic_handler.py to call divide with a divisor of 0:

def divide(dividend , divisor):
    print(dividend / divisor)

divide(dividend=10, divisor=0)

Run the script again:

python3 basic_handler.py

This time, the program crashes and prints a traceback, indicating a ZeroDivisionError:

Traceback (most recent call last):
  File "/home/labex/project/basic_handler.py", line 4, in <module>
    divide(dividend=10, divisor=0)
  File "/home/labex/project/basic_handler.py", line 2, in divide
    print(dividend / divisor)
ZeroDivisionError: division by zero

To prevent the program from crashing and instead show a user-friendly message, we can "handle" the exception using try and except. The code that might cause an error is placed inside the try block, and the code to execute if that error occurs is placed in the except block.

Update the basic_handler.py file with the following code to handle the ZeroDivisionError gracefully:

## try-except: handle exceptions gracefully
def divide(dividend , divisor):
    try:  ## Try to execute this code
        print(dividend / divisor)
    except ZeroDivisionError:  ## Catch specific exception type
        print('You can not divide by 0')

divide(dividend=10, divisor=0)

Now, run the script one more time:

python3 basic_handler.py

Instead of crashing, the program now prints the helpful message you defined, and execution finishes normally:

You can not divide by 0

Handling Multiple Exceptions Using One Exception Block

In this step, you will discover how to handle multiple different exception types within a single except block. This is useful for streamlining your error-handling code when several types of errors can occur.

You can catch a tuple of exceptions, such as (ZeroDivisionError, TypeError), to handle them with the same logic.

Open the file multiple_handler.py and add the following code. This function is designed to potentially raise either a TypeError (by trying to concatenate a string and an integer) or a ZeroDivisionError.

## Handle multiple exceptions in one except block
def divide(dividend , divisor):
    try:
        if (dividend == 10):
          var = 'str' + 1  ## This will raise TypeError
        else:
          print(dividend / divisor)
    except (ZeroDivisionError, TypeError) as error:  ## Catch multiple exception types
        print(error)  ## Print the error message

divide(dividend=20, divisor=5)

First, let's run the code in a scenario where no exception occurs. Execute the script:

python3 multiple_handler.py

The output will be the result of the division:

4.0

Now, let's trigger the TypeError. Modify the last line of multiple_handler.py to use 10 as the dividend:

## ... (rest of the code) ...
divide(dividend=10, divisor=5)

Run the script again:

python3 multiple_handler.py

The except block catches the TypeError and prints the associated error message:

can only concatenate str (not "int") to str

Finally, let's trigger the ZeroDivisionError. Modify the last line again, this time attempting to divide by zero. Note that the TypeError condition (dividend == 10) is not met.

## ... (rest of the code) ...
divide(dividend=20, divisor=0)

Run the script:

python3 multiple_handler.py

The same except block now catches the ZeroDivisionError and prints its message:

division by zero

For the verification to pass, please ensure the final call in your script is divide(dividend=10, divisor=5) to trigger the TypeError.

Finally Code in Exception Handling

In this step, you will understand the purpose and usage of the finally block. The code within a finally clause is always executed, regardless of whether an exception was raised or handled. This makes it ideal for cleanup operations, such as closing a file or a network connection.

Let's see how it works. Open the file finally_handler.py and add the following code:

## finally block: always executes regardless of exceptions
def divide(dividend , divisor):
    try:
        print(dividend / divisor)
    except ZeroDivisionError:
        print('You can not divide by 0')
    finally:  ## Always executes, even if exception occurs
        print('Execution finished')

divide(dividend=10, divisor=5)

Run the script from your terminal:

python3 finally_handler.py

In this case, no exception occurs. The try block completes successfully, and then the finally block is executed.

2.0
Execution finished

Now, let's introduce an error. Modify the last line in finally_handler.py to divide by zero:

## ... (rest of the code) ...
divide(dividend=10, divisor=0)

Run the script again:

python3 finally_handler.py

This time, a ZeroDivisionError is raised and caught by the except block. Even though an exception occurred, the finally block is still executed after the except block is finished.

You can not divide by 0
Execution finished

As you can see, the "Execution finished" message is printed in both scenarios, demonstrating that the finally block is a reliable place for code that must always run.

Custom Exceptions

In this step, you will learn how to create your own custom exceptions. This is a powerful feature for making your applications more readable and maintainable, as you can create specific error types that describe problems unique to your program's domain.

Custom exceptions are defined by creating a new class that inherits from Python's base Exception class. You can then trigger this exception using the raise keyword.

Open the file custom_exception.py and add the following code to define and raise a simple custom exception:

## Custom exception: create by inheriting from Exception class
class MyCustomException(Exception):
    pass

raise MyCustomException  ## Raise the custom exception

If you run this script, it will immediately stop and show a traceback for your new exception:

python3 custom_exception.py
Traceback (most recent call last):
  File "/home/labex/project/custom_exception.py", line 5, in <module>
    raise MyCustomException  ## Raise the custom exception
__main__.MyCustomException

You can also provide a descriptive message when you raise a custom exception. Modify custom_exception.py to pass a string to your exception:

class MyCustomException(Exception):
    pass

raise MyCustomException('A custom message for my custom exception')

Running this will show your message in the traceback:

Traceback (most recent call last):
  File "/home/labex/project/custom_exception.py", line 4, in <module>
    raise MyCustomException('A custom message for my custom exception')
__main__.MyCustomException: A custom message for my custom exception

Handling a custom exception is done the same way as handling a built-in one: using a try...except block. Update custom_exception.py to its final form, which raises and handles the custom exception:

class MyCustomException(Exception):
    pass

try:
    raise MyCustomException('A custom message for my custom exception')
except MyCustomException:
    print('My custom exception was raised')

Now, run the script for the last time:

python3 custom_exception.py

The program no longer crashes. Instead, it executes the code in the except block and prints your confirmation message:

My custom exception was raised

Summary

In this lab, you have learned the essential techniques for handling exceptions in Python. You started with the basic try and except blocks to catch specific errors and prevent program crashes. You then learned how to efficiently handle multiple exception types in a single except block. You also explored the finally block, which is crucial for cleanup code that must always run. Finally, you gained the ability to create, raise, and handle your own custom exceptions, allowing you to build more robust and descriptive error-handling logic in your applications.