PYTHON

How to Handle Exceptions in Python: A Detailed Visual Introduction

In this article, you will learn how to handle exceptions in Python.

In particular, we’ll cover:

  • Exceptions
  • The purpose of exception handling
  • The try clause
  • The except clause
  • The else clause
  • The finally clause
  • How to raise exceptions

Are you ready? Let’s begin!

1. Intro to Exceptions

We will start with exceptions:

What are they?

Why are they relevant?

Why do you have to handle them?

According to the Python documentation:

Errors detected during execution are called exceptions and aren’t unconditionally fatal.

Exceptions are raised when the program encounters a mistake during its execution. They disrupt the traditional flow of the program and typically end it abruptly. To avoid this, you’ll catch them and handle them appropriately.

You’ve probably seen them during your programming projects.

If you have ever tried to divide by zero in Python, you want to have seen this error message:

>>> a = 5/0
Traceback (most recent call last):
  File "<pyshell#1>", line 1, in <module>
    a = 5/0
ZeroDivisionError: division by zero

If you tried to index a string with an invalid index, you definitely got this error message:

>>> a = "Hello, World"
>>> a[456]
Traceback (most recent call last):
  File "<pyshell#3>", line 1, in <module>
    a[456]
IndexError: string index out of range

These are examples of exceptions.

Common Exceptions
There are many various sorts of exceptions, and that they are all raised especially situations. a number of the exceptions that you simply will presumably see as you’re employed on your projects are:

IndexError – raised once you attempt to index an inventory , tuple, or string beyond the permitted boundaries. For example:

>>> num = [1, 2, 6, 5]
>>> num[56546546]
Traceback (most recent call last):
  File "<pyshell#7>", line 1, in <module>
    num[56546546]
IndexError: list index out of range

KeyError – raised once you attempt to access the worth of a key that does not exist during a dictionary. For example:

>>> students = {"Nora": 15, "Gino": 30}
>>> students["Lisa"]
Traceback (most recent call last):
  File "<pyshell#9>", line 1, in <module>
    students["Lisa"]
KeyError: 'Lisa'

NameError – raised when a reputation that you simply are referencing within the code doesn’t exist. For example:

>>> a = b
Traceback (most recent call last):
  File "<pyshell#10>", line 1, in <module>
    a = b
NameError: name 'b' is not defined

TypeError – raised when an operation or function is applied to an object of an inappropriate type. For example:

>>> (5, 6, 7) * (1, 2, 3)
Traceback (most recent call last):
  File "<pyshell#12>", line 1, in <module>
    (5, 6, 7) * (1, 2, 3)
TypeError: can't multiply sequence by non-int of type 'tuple'

ZeroDivisionError – raised once you attempt to divide by zero.

>>> a = 5/0
Traceback (most recent call last):
  File "<pyshell#13>", line 1, in <module>
    a = 5/0
ZeroDivisionError: division by zero

Anatomy of an Exception
I’m sure that you simply must have noticed a general pattern in these error messages. Let’s break down their general structure piece by piece:

First, we discover this line (see below). A traceback is essentially an inventory detailing the function calls that were made before the exception was raised.

The traceback helps you during the debugging process because you’ll analyze the sequence of function calls that resulted within the exception:

Traceback (most recent call last):

Then, we see this line (see below) with the trail to the file and therefore the line that raised the exception. during this case, the trail was the Python shell since the instance was executed directly in IDLE.

File "<pyshell#0>", line 1, in <module>
   a - 5/0

Tip: If the road that raised the exception belongs to a function, <module> is replaced by the name of the function.

Finally, we see a descriptive message detailing the sort of exception and providing additional information to assist us debug the code:

NameError: name 'a' is not defined

2.Exception Handling: Purpose & Context

You may ask: why would i would like to handle exceptions? Why is that this helpful for me? By handling exceptions, you’ll provide an alternate flow of execution to avoid crashing your program unexpectedly.

 Example: User Input

Imagine what would happen if a user who is functioning together with your program enters an invalid input. this is able to raise an exception because an invalid operation was performed during the method .

If your program doesn’t handle this correctly, it’ll crash suddenly and therefore the user will have a really disappointing experience together with your product.

READ  Lowest Common Ancestor in a Binary Search Tree

But if you are doing handle the exception, you’ll be ready to provide an alternate to enhance the experience of the user.

Perhaps you’ll display a descriptive message asking the user to enter a legitimate input, otherwise you could provide a default value for the input. counting on the context, you’ll choose what to try to to when this happens, and this is often the magic of error handling. It can save the day when unexpected things happen.

 What Happens Behind the Scenes?

Basically, once we handle an exception, we are telling the program what to try to to if the exception is raised. therein case, the “alternative” flow of execution will come to the rescue. If no exceptions are raised, the code will run needless to say .

3.Time to Code: The try … except Statement

Now that you simply know what exceptions are and why you ought to we handle them, we’ll start diving into the built-in tools that the Python languages offers for this purpose.

First, we’ve the foremost basic statement: try … except.

Let’s illustrate this with an easy example. we’ve this small program that asks the user to enter the name of a student to display his/her age:

students = {"Nora": 15, "Gino": 30}

def print_student_age():
    name = input("Please enter the name of the student: ")
    print(students[name])

print_student_age()

Notice how we aren’t validating user input at the instant , therefore the user might enter invalid values (names that aren’t within the dictionary) and therefore the consequences would be catastrophic because the program would crash if a KeyError is raised:

# User Input
Please enter the name of the student: "Daniel"

# Error Message
Traceback (most recent call last):
  File "<path>", line 15, in <module>
    print_student_age()
  File "<path>", line 13, in print_student_age
    print(students[name])
KeyError: '"Daniel

Syntax

We can handle this nicely using try … except. This is the basic syntax:

In our example, we would add the try … except statement within the function. Let’s break this down piece by piece:

students = {"Nora": 15, "Gino": 30}

def print_student_age():
    while True:
        try:
            name = input("Please enter the name of the student: ")
            print(students[name])
            break
        except:
            print("This name is not registered")
    

print_student_age()

If we “zoom in”, we see the try … except statement:

try:
	name = input("Please enter the name of the student: ")
	print(students[name])
	break
except:
	print("This name is not registered")
  • When the function is named , the try clause will run. If no exceptions are raised, the program will run needless to say .
  • But if an exception is raised within the try clause, the flow of execution will immediately jump to the except clause to handle the exception.

 Note:  This code is contained within a short time loop to continue posing for user input if the worth is invalid. this is often an example:

Please enter the name of the student: "Lulu"
This name is not registered
Please enter the name of the student: 

This is great, right? Now we will continue posing for user input if the worth is invalid.

At the instant , we are handling all possible exceptions with an equivalent except clause. But what if we only want to handle a selected sort of exception? let’s examine how we could do that .

 Catching Specific Exceptions

Since not all kinds of exceptions are handled within the same way, we will specify which exceptions we might wish to handle with this syntax:

This is an example. We are handling the ZeroDivisionError exception just in case the user enters zero because the denominator:

def divide_integers():
    while True:
        try:
            a = int(input("Please enter the numerator: "))
            b = int(input("Please enter the denominator: "))
            print(a / b)
        except ZeroDivisionError:
            print("Please enter a valid denominator.")


divide_integers()

This would be the result:

# First iteration
Please enter the numerator: 5
Please enter the denominator: 0
Please enter a valid denominator. 

# Second iteration
Please enter the numerator: 5
Please enter the denominator: 2
2.5

We are handling this correctly. But… if another sort of exception is raised, the program won’t handle it gracefully.

Here we’ve an example of a ValueError because one among the values may be a float, not an int:

Please enter the numerator: 5
Please enter the denominator: 0.5
Traceback (most recent call last):
  File "<path>", line 53, in <module>
    divide_integers()
  File "<path>", line 47, in divide_integers
    b = int(input("Please enter the denominator: "))
ValueError: invalid literal for int() with base 10: '0.5'

We can customize how we handle differing types of exceptions.

READ  Python algorithm - Breadth First Traversal or BFS for a Graph

📌 Multiple Except Clauses

To do this, we’d like to feature multiple except clauses to handle differing types of exceptions differently.

According to the Python Documentation:

A try statement may have quite one except clause, to specify handlers for various exceptions. at the most one handler are going to be executed.

In this example, we’ve two except clauses. one among them handles ZeroDivisionError and therefore the other one handles ValueError, the 2 sorts of exceptions that would be raised during this try block.

def divide_integers():
    while True:
        try:
            a = int(input("Please enter the numerator: "))
            b = int(input("Please enter the denominator: "))
            print(a / b)
        except ZeroDivisionError:
            print("Please enter a valid denominator.")
        except ValueError:
            print("Both values have to be integers.")


divide_integers() 

Tip: you’ve got to work out which sorts of exceptions could be raised within the try block to handle them appropriately.

Multiple Exceptions, One Except Clause

You can also prefer to handle differing types of exceptions with an equivalent except clause.

According to the Python Documentation:

An except clause may name multiple exceptions as a parenthesized tuple.

This is an example where we catch two exceptions (ZeroDivisionError and ValueError) with an equivalent except clause:

def divide_integers():
    while True:
        try:
            a = int(input("Please enter the numerator: "))
            b = int(input("Please enter the denominator: "))
            print(a / b)
        except (ZeroDivisionError, ValueError):
            print("Please enter valid integers.")

divide_integers()

 Handling Exceptions Raised by Functions Called within the try Clause

Please enter the numerator: 5
Please enter the denominator: 0
Please enter valid integers.
Please enter the numerator: 0.5
Please enter valid integers.
Please enter the numerator: 

Handling Exceptions Raised by Functions Called in the try Clause

An interesting aspect of exception handling is that if an exception is raised during a function that was previously called within the try clause of another function and therefore the function itself doesn’t handle it, the caller will handle it if there’s an appropriate except clause.

According to the Python Documentation:

Exception handlers don’t just handle exceptions if they occur immediately within the try clause, but also if they occur inside functions that are called (even indirectly) within the try clause.

Let’s see an example for instance this:

def f(i):
    try:
        g(i)
    except IndexError:
        print("Please enter a valid index")

def g(i):
    a = "Hello"
    return a[i]

f(50)

We have the f function and therefore the g function. f calls g within the try clause. With the argument 50, g will raise an IndexError because the index 50 isn’t valid for the string a.

But g itself doesn’t handle the exception. Notice how there’s no try … except statement within the g function. Since it doesn’t handle the exception, it “sends” it to f to ascertain if it can handle it, as you’ll see within the diagram below:

Since f does know how to handle an IndexError, the situation is handled gracefully and this is the output:

Please enter a valid index

Note: If f had not handled the exception, the program would have ended abruptly with the default error message for an IndexError.

Accessing Specific Details of Exceptions

Exceptions are objects in Python, so you’ll assign the exception that was raised to a variable. This way, you’ll print the default description of the exception and access its arguments.

According to the Python Documentation:

The except clause may specify a variable after the exception name. The variable is sure to an exception instance with the arguments stored in instance.args.

Here we’ve an example (see below) were we assign the instance of ZeroDivisionError to the variable e. Then, we will use this variable within the except clause to access the sort of the exception, its message, and arguments.

def divide_integers():
    while True:
        try:
            a = int(input("Please enter the numerator: "))
            b = int(input("Please enter the denominator: "))
            print(a / b)
        # Here we assign the exception to the variable e
        except ZeroDivisionError as e:
            print(type(e))
            print(e)
            print(e.args)

divide_integers()

The corresponding output would be:

Please enter the numerator: 5
Please enter the denominator: 0

# Type
<class 'ZeroDivisionError'>

# Message
division by zero

# Args
('division by zero',)

Tip: If you’re conversant in special methods, consistent with the Python Documentation: “for convenience, the exception instance defines __str__() therefore the arguments are often printed directly without having to reference .args.

4.Now Let’s Add: The “else” Clause

The else clause is optional, but it is a useful gizmo because it lets us execute code that ought to only run if no exceptions were raised within the try clause.

READ  If, Elif, and Else Statements in Python

According to the Python Documentation:

The try … except statement has an optional else clause, which, when present, must follow all except clauses. it’s useful for code that has got to be executed if the try clause doesn’t raise an exception.

Here is an example of the utilization of the else clause:

def divide_integers():
    while True:
        try:
            a = int(input("Please enter the numerator: "))
            b = int(input("Please enter the denominator: "))
            result = a / b
        except (ZeroDivisionError, ValueError):
            print("Please enter valid integers. The denominator can't be zero")
        else:
            print(result)

divide_integers()

If no exception are raised, the result is printed:

Please enter the numerator: 5
Please enter the denominator: 5
1.0

But if an exception is raised, the result is not printed:

Please enter the numerator: 5
Please enter the denominator: 0
Please enter valid integers. The denominator can't be zero

 Tip:According to the Python Documentation:

The use of the else clause is best than adding additional code to the try clause because it avoids accidentally catching an exception that wasn’t raised by the code being protected by the try … except statement.

5. The “finally” Clause

The finally clause is that the last clause during this sequence. it’s optional, but if you include it, it’s to be the last clause within the sequence. The finally clause is usually executed, albeit an exception was raised within the try clause.

According to the Python Documentation:

If a finally clause is present, the finally clause will execute because the last task before the try statement completes. The finally clause runs whether or not the try statement produces an exception.

The finally clause is typically wont to perform “clean-up” actions that ought to always be completed. for instance , if we are working with a enter the try clause, we’ll always got to close the file, albeit an exception was raised once we were working with the info .

Here is an example of the finally clause:

def divide_integers():
    while True:
        try:
            a = int(input("Please enter the numerator: "))
            b = int(input("Please enter the denominator: "))
            result = a / b
        except (ZeroDivisionError, ValueError):
            print("Please enter valid integers. The denominator can't be zero")
        else:
            print(result)
        finally:
            print("Inside the finally clause")

divide_integers()

This is the output when no exceptions were raised:

Please enter the numerator: 5
Please enter the denominator: 5
1.0
Inside the finally clause

This is the output when an exception was raised:

Please enter the numerator: 5
Please enter the denominator: 0
Please enter valid integers. The denominator can't be zero
Inside the finally clause

Notice how the finally clause always runs.

 Important: remember that the else clause and therefore the finally clause are optional, but if you opt to incorporate both, the finally clause has got to be the last clause within the sequence.

6. Raising Exceptions

Now that you simply skills to handle exceptions in Python, i might wish to share with you this useful tip: you’ll also choose when to boost exceptions in your code.

This can be helpful surely scenarios. let’s examine how you’ll do this:

This line will raise a ValueError with a custom message.

Here we’ve an example (see below) of a function that prints the worth of the things of an inventory or tuple, or the characters during a string. But you made the decision that you simply want the list, tuple, or string to be of length 5. you begin the function with an if statement that checks if the length of the argument data is 5. If it is not , a ValueError exception is raised:

def print_five_items(data):
    
    if len(data) != 5:
        raise ValueError("The argument must have five elements")
    
    for item in data:
        print(item)

print_five_items([5, 2])

The output would be:

Traceback (most recent call last):
  File "<path>", line 122, in <module>
    print_five_items([5, 2])
  File "<path>", line 117, in print_five_items
    raise ValueError("The argument must have five elements")
ValueError: The argument must have five elements

Notice how the last line displays the descriptive message:

How to Handle Exceptions in Python: A Detailed Visual Introduction

ValueError: The argument must have five elements

You can then choose the way to handle the exception with a try … except statement. you’ll add an else clause and/or a finally clause. you’ll customize it to suit your needs.

About the author

Wikitechy Editor

Wikitechy Editor

Wikitechy Founder, Author, International Speaker, and Job Consultant. My role as the CEO of Wikitechy, I help businesses build their next generation digital platforms and help with their product innovation and growth strategy. I'm a frequent speaker at tech conferences and events.

X