Error Handling

Learning Objectives

By the end of this chapter, you will be able to:

  • Describe what errors and exceptions are
  • Handle errors and exceptions when they occur
  • Define and use your own custom exceptions

This lesson describes error handling in Python. We look at the try…except clause and its modified types. Lastly, we cover custom exceptions. Solutions for the activities in this lesson can be found on page 289.

Introduction

In life, things sometimes do not go according to plan. You may find, for example, that you have budgeted to buy some things for when you go to the store. But when you actually arrive at the store, you see some items that are not on your list are on sale and you buy them! That is an incident where the initial plan was not executed well and did not produce the expected results.

A similar scenario can arise while programming. When you write some code and run it, unexpected situations can occur, which may cause the code not to be executed correctly or not produce the expected results. For example, there could be a problem with syntax, an undefined variable you are trying to use, or even a completely unforeseen scenario. When code does not execute as intended, we say that an error has occurred.

Some errors can be logical errors. These can occur when specifications are not followed. For example, this could be a function that is supposed to return the sum of two numbers but actually returns the product. This is a logical error and is not the kind of error we will be tackling in this module. Instead, we will focus more on the runtime errors you are likely to see when the Python interpreter encounters problems during code execution.

Note

For more information on how to handle logical errors, look at this excellent guide on unit testing at https://docs.python-guide.org/writing/tests/.

By now, you will have probably encountered many errors while coding in Python. This chapter aims to equip you with a better understanding of why errors occur and what to do about them when they do. This helps prevent scenarios where, for example, an error occurs on your application and because it is not handled well, brings the whole application down.

Errors and Exceptions in Python

You may have noticed that we mention both errors and exceptions and use them in an almost interchangeable way. Why is that? Are they the same thing? Usually, they are. But in Python there are some slight differences in the meanings of the two words.

Exceptions are errors that occur when your program is running, while errors, for example, syntax errors, occur before program execution happens.

How to Raise Exceptions

You can raise exceptions yourself for one reason or another by using the raise keyword. You will want to raise exceptions when something occurs and you want to inform users of your app, for example, the input given is incorrect. Let's try out an example:

def raise_an_error(error):

raise error

raise_an_error(ValueError)

In our example, we define a function called raise_an_error(), which takes the error class name and raises it. We then try out the code by calling it with the built-in ValueError exception class.

If all goes well, you should see an output like this when running the script:

Traceback (most recent call last):

File "python", line 4, in <module>

File "python", line 2, in raise_an_error

ValueError

Built-In Exceptions

Python ships with a ton of built-in exception classes to cover a lot of error situations so that you do not have to define your own. These classes are divided into Base error classes, from which other error classes are defined, and Concrete error classes, which define the exceptions you are more likely to see from time to time.

Note

Built-in exception classes cover many error situations so that you do not have to define your own. For more information on built-in exceptions, visit https://docs.python.org/3/library/exceptions.html.

We shall cover more on the Exception base class and its uses later on in this chapter. For now, let's take a look at some common error and exception classes and understand what they mean.

SyntaxError

A SyntaxError is very common, especially if you are new to Python. It occurs when you type a line of code which the Python interpreter is unable to parse.

Here is an example:

def raise_an_error(error)

raise error

raise_an_error(ValueError)

In this implementation of our previous raise_an_error() method, we made a deliberate syntax error. Can you guess what it is?

Running the previous script will output the following:

Traceback (most recent call last):

File "python", line 1

def raise_an_error(error)

ˆ

SyntaxError: invalid syntax

You can see that a SyntaxError was raised with the message invalid syntax. You can also see from the stack trace the exact line the error occurred on and a small ^ pointing to the source of the error, in this case, the omission of : in the function signature. Adding it will enable the interpreter to parse the line successfully and move on with execution.

ImportError

An ImportError occurs when an import cannot be resolved.

For example, importing a non-existent module will raise a ModuleNotFoundError, that is a subclass of the ImportError class:

>>> import nonexistentmodule

Traceback (most recent call last):

File "python", line 1, in <module>

ModuleNotFoundError: No module named 'nonexistentmodule'

KeyError

A KeyError occurs when a dictionary key is not found while trying to access it. Here's an example:

person = {

"name": "Rich Brown",

"age": 56

}

print(person["gender"])

The person dictionary defined here has only two keys: name and age. Attempting to read a key called gender raises a KeyError:

Traceback (most recent call last):

File "python", line 6, in <module>

KeyError: 'gender'

A simple way of mitigating a KeyError is to use the get() method that is defined on dictionaries, when accessing keys, which will return None or a custom value if the key is non-existent:

person = {

"name": "Rich Brown",

"age": 56

}

print(person.get("gender"))

This will output None.

TypeError

A TypeError will occur if you attempt to do an operation on a value or object of the wrong type.

Here are a few examples:

Adding a string to an int results in the following error:

>>> "string" + 8

Traceback (most recent call last):

File "<input>", line 1, in <module>

"string" + 8

TypeError: must be str, not int

This also occurs when passing wrong arguments (for example, passing an integer when we expect a list):

a = 6

for index, value in enumerate(a):

print(value)

This will also result in a TypeError:

Traceback (most recent call last):

File "python", line 3, in <module>

TypeError: 'int' object is not iterable

The error displayed means that we cannot loop over an int object.

AttributeError

An AttributeError is raised when assigning or referencing an attribute fails.

Here is an example. We are going to try and call a method called push on a list object:

a = [1,2,3]

a.push(4)

An AttributeError is thrown because the list object has no attribute called push:

Traceback (most recent call last):

File "python", line 3, in <module>

AttributeError: 'list' object has no attribute 'push'

Note

Remember, to add a value to the end of a list, use the append() method.

IndexError

An IndexError occurs if you are trying to access an index (for example, in a list) which does not exist.

Here is an example:

a = [1,2,3]

print(a[3])

The list, a, only has three indexes: 0, 1, and 2. Attempting to access index 3 will cause the interpreter to throw an IndexError:

Traceback (most recent call last):

File "python", line 3, in <module>

IndexError: list index out of range

NameError

A NameError occurs when a specified name cannot be found either locally or globally. This usually happens because the name or variable is not defined.

For example, printing any undefined variable should throw a NameError:

>>> print(age)

Traceback (most recent call last):

File "python", line 1, in <module>

NameError: name 'age' is not defined

FileNotFoundError

The last Exception class we will cover in this section is the FileNotFoundError.

This error is raised if a file you are attempting to read or write is not found.

Here is an example:

with open('input.txt', 'r') as myinputfile:

for line in myinputfile:

print(line)

The preceding code attempts to read a file called input.txt. Since we have deliberately not created any such file on our environment, we get a FileNotFoundError:

Traceback (most recent call last):

File "python", line 1, in <module>

FileNotFoundError: [Errno 2] No such file or directory: 'input.txt'

Activity 39: Identifying Error Scenarios

During programming, errors often occur. But how can we anticipate when something will cause an error? By thoroughly understanding error scenarios and causes. In this activity, we will create error scenarios. Let's write some code that will cause the following errors:

The steps are as follows:

KeyError

  1. Create the following dictionary:

    building = dict(

    name="XYZ Towers",

    type="Business Premises"

    )

  2. Write a print statement that uses a key that's not defined in this dictionary.

AttributeError

  1. Import the string module.
  2. Use an attribute that's not defined in the string module.

    Note

    Solution for this activity can be found at page 296.

Handling Errors and Exceptions

Handling errors and exceptions starts long before you get to running your code. Right from the planning phase, you should have contingencies in place to avoid running into errors, especially logical errors that may be harder to catch in some cases.

Practices such as defensive programming can help mitigate future errors in some cases.

According to Wikipedia:

"Defensive programming is a form of defensive design intended to ensure the continuing function of a piece of software under unforeseen circumstances. Defensive programming practices are often used where high availability, safety, or security is needed."

Defensive programming is an approach that's used to improve software and source code, in terms of the following:

  • General quality—by reducing the number of software bugs and problems.
  • Making the source code comprehensible—the source code should be readable and understandable, so that it is approved in a code audit.
  • Making the software behave in a predictable manner, despite unexpected inputs or user actions.

In this section, we aim to show you some basic error handling in Python so that the next time errors occur, they do not bring your program to a crashing halt.

Exercise 49: Implementing the try…except Block

The simplest way to handle errors is to use the try…except block. The code in the try section is executed and if an error, which is specified in the except block, is thrown, the code in the except block is executed.

Once the block finishes executing, the rest of the code executes as well. This prevents errors from causing your program to crash.

Let's see an example. We are going to use the code example that we used to describe the FileNotFoundError in the previous section to demonstrate the try…except block:

  1. Write the following script:

    with open('input.txt', 'r') as myinputfile:

    for line in myinputfile:

    print(line)

    print("Execution never gets here")

  2. Run this script to get the following output:

    Traceback (most recent call last):

    File "python", line 1, in <module>

    FileNotFoundError: [Errno 2] No such file or directory: 'input.txt'

    As you can see, the error caused the execution to stop before the last line.

  3. Rewrite the same code with the FileNotFoundError handled using a try…except block:

    try:

    with open('input.txt', 'r') as myinputfile:

    for line in myinputfile:

    print(line)

    except FileNotFoundError:

    print("Whoops! File does not exist.")

    print("Execution will continue to here.")

    After wrapping the code in a try…except block, instead of crashing, the script executes the code in the except block and continues to next line:

    Whoops! File does not exist.

    Execution will continue to here.

  4. Note that the way we have written our code means that if any other exception occurs, it will be unhandled and the code will still crash.

    You can handle more than one exception by creating a tuple, like this:

    try:

    with open('input.txt', 'r') as myinputfile:

    for line in myinputfile:

    print(line)

    except (FileNotFoundError, ValueError):

    print("Whoops! File does not exist.")

    print("Execution will continue to here.")

  5. A better way is to handle them individually and do something different for each error you get. Implement this as follows:

    try:

    with open('input.txt', 'r') as myinputfile:

    for line in myinputfile:

    print(line)

    except FileNotFoundError:

    print("Whoops! File does not exist.")

    except ValueError:

    print("A value error occurred")

    In our case, only the except clause for the FileNotFoundError will be executed. However, if a ValueError also occurs, both except clauses will be executed.

  6. If you are not quite sure which exception will be thrown, you can catch the generic Exception, which will catch any exception that's thrown. It is a good practice to catch the generic Exception at the end of more specific except clauses and not by itself.

    Implement it like this:

    try:

    with open('input.txt', 'r') as myinputfile:

    for line in myinputfile:

    print(line)

    except FileNotFoundError:

    print("Whoops! File does not exist.")

    except ValueError:

    print("A value error occurred")

    except Exception:

    print("Something unforeseen happened")

    print("Execution will continue to here.")

    However, doing this is a bad practice:

    try:

    with open('input.txt', 'r') as myinputfile:

    for line in myinputfile:

    print(line)

    except Exception:

    print("Something unforeseen happened")

    print("Execution will continue to here.")

    However, both approaches are valid syntax and will work just fine.

    Note

    Python will not allow you to catch syntax errors. These should always be fixed before your code can run at all.

Exercise 50: Implementing the try…except…else Block

In this exercise, we will implement the try…except block with an additional else statement.

The try…except…else block is a minor modification of the traditional try…except block so that it can include an else block. The code in the else block is always executed if no error has occurred.

  1. Implement the try…except…else block as follows:

    try:

    with open('input.txt', 'r') as myinputfile:

    for line in myinputfile:

    print(line)

    except FileNotFoundError:

    print("Whoops! File does not exist.")

    except ValueError:

    print("A value error occurred")

    except Exception:

    print("Something unforeseen happened")

    else:

    print("No error because file exists")

    print("Execution will continue to here.")

  2. Run the preceding script; if an error is thrown, the output will be as follows:

    Whoops! File does not exist.

    Execution will continue to here.

    The output in the case of no error being thrown will be as follows:

    No error because file exists

    Execution will continue to here.

    Note

    The output shown here ignores the contents of input.txt printed by print(line) since it is not relevant to the try…except logic.

Exercise 51: Implementing the finally Keyword

The finally keyword defines a code block that must execute before the try…except block exits, irrespective of whether any exception occurred.

It is usually the last block in the try…except block after all the exception handling logic and will always be executed:

  1. Continuing with our file reading example, the finally keyword can be implemented like this:

    try:

    with open('input.txt', 'r') as myinputfile:

    for line in myinputfile:

    print(line)

    except FileNotFoundError:

    print("Whoops! File does not exist.")

    except ValueError:

    print("A value error occurred")

    except Exception:

    print("Something unforeseen happened")

    finally:

    print("I will always show up")

    print("Execution will continue to here.")

  2. Run this script; the output when the FileNotFoundError occurs will look like this:

    Whoops! File does not exist.

    I will always show up

    Execution will continue to here.

    If the file exists, the output will look like this:

    I will always show up

    Execution will continue to here.

The finally keyword is useful, for example, in cases where some clean-up logic needs to happen. This might be closing files, closing database connections, or releasing system resources.

Activity 40: Handling Errors

You are completing some code, but you have an unhandled error. What do you do to make sure that the error doesn't stop your program prematurely? In this activity, we will practice handling errors.

The following code throws an error:

import random

print(random.randinteger(1,10))

Identify and handle the error so that when it occurs, the message Oops! Something went wrong is printed to the terminal.

Note

Solution for this activity can be found at page 297.

Custom Exceptions

Built-in exceptions cover a wide range of situations. Sometimes, however, you may need to define a custom exception to fit your specific application situation; for example, a RecipeNotValidError exception for when a recipe is not valid in your cooking app.

In this case, Python contains the ability to add custom errors by extending the base Exception class.

Implementing Your Own Exception Class

Exceptions should be named with names ending with the word Error. Let's create the RecipeNotValidError we talked about previously as a custom exception:

class RecipeNotValidError(Exception):

def __init__(self):

self.message = "Your recipe is not valid"

try:

raise RecipeNotValidError

except RecipeNotValidError as e:

print(e.message)

The custom exception class should just contain a few attributes that will help the user get more information about what error occurred. Our sample implementation has the message attribute, which we have used to get details on the error message. This is the output you would get if you run the preceding snippet:

Your recipe is not valid

Activity 41: Creating Your Own Custom Exception Class

As a developer, you may need to create exception classes that handle custom exceptions that are not defined in the standard library in a certain way. In this activity, we will practice creating custom exception classes.

The steps are as follows:

  1. Subclass the Exception class and create a new Exception class of your choosing.
  2. Write some code where you use the try…except flow to capture and handle your custom exception.

    Note

    Solution for this activity can be found at page 297.

Summary

In this chapter, we talked about errors and exceptions, what they are, and how to avoid them. We looked at a few built-in errors and the scenarios that would cause them to be raised. We then moved on to handling them by using try…except and finally blocks. We also covered how to implement our own custom exceptions by using the Exception base class.

This should give you the ability to make your programs more robust by handling both seen and unforeseen issues that may arise during code execution. Handling errors should also help prevent unpleasant usability or security issues from cropping up when your code is in the wild.

This book is an introduction to the general-purpose language Python. We have covered topics such as variable names; working with functions; modules; data structures such as lists, tuples, and dictionaries; and even working with files. This book is designed to get you from beginner level to intermediate Python developer level. We hope that you have enjoyed this experience. Feel free to go back to the chapter activities to refresh your memory regarding what you have learned.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.138.204.186