When working with scripts, you will frequently experience errors referred to as exceptions. Understanding what exceptions are and how to handle them will make you more adept in troubleshooting script failures and writing reliable code.

What You’ll Learn

What You’ll Need

An exception is an unexpected event that occurs during the execution of a program that disrupts the normal flow of the program’s instructions.

Carefully analyze the divide.py script being run below. Does the script appear to be executing correctly? Are there any errors? If so, which errors do you see?

linux$ python divide.py
  File "/home/expert/Documents/divide.py", line 1
    num1 = int(input( 'Enter a number you want to divide: '))
IndentationError: unexpected indent

The above script failed to execute, and an IndentationError exception was raised.

Exceptions being raised when writing scripts can be a frustrating experience. However, exceptions do serve a purpose. Exceptions notify the developer of the type of problem the script is experiencing by displaying the following:

Before we raise our own exceptions, let’s write a usable script that divides numbers. Write the script below, and save the file as divide.py.

num1 = int(input( 'Enter a number you want to divide: '))
num2 = int(input( 'Enter the number of times you want to divide the previous number by: '))

print(f'{num1} divided by {num2} is: {num1 / num2}')

Run and then input integers into the script to see the normal execution of the script.

linux$ python divide.py
Enter a number you want to divide: 10
Enter the number of times you want to divide the previous number by: 2
10 divided by 2 is: 5.0

Let’s now raise exceptions. Make a syntax error in the divide.py script. Delete the last closed parenthesis in the first line of code.

script

Save and then run the script. The normal flow of the script should be disrupted and an exception should be raised.

Carefully analyze the output of the exception. Attempt to answer these questions:

linux$ python divide.py
  File "/home/expert/Documents/divide.py", line 2
    num2 = int(input( 'Enter the number of times you want to divide the previous number by: '))
    ^
SyntaxError: invalid syntax

In our example, we are receiving a SyntaxError exception. The exception message is invalid syntax, and the beginning of line 2 could potentially be causing the exception to be raised.

SyntaxError exceptions are raised when your Python code does not adhere to Python syntax rules. Our script is missing a parenthesis at the end of line 1, and because of this, our script is raising a SyntaxError exception.

Resolve the SyntaxError exception by re-adding the last parenthesis in line 1.

script

Save and re-run the script to confirm the script executes normally.

linux$ python divide.py
Enter a number you want to divide: 10
Enter the number of times you want to divide the previous number by: 2
10 divided by 2 is: 5.0

A SyntaxError exception is one of many different types of built-in exceptions that can commonly be raised in Python.

Another type of exception frequently raised is an IndentationError exception. Indent the code in line 1 a single space to the right to raise an IndentationError exception.

script

Save and run the script.

linux$ python divide.py
  File "/home/expert/Documents/divide.py", line 1
    num1 = int(input( 'Enter a number you want to divide: '))
IndentationError: unexpected indent

Notice the normal flow of the script is disrupted and an IndentationError exception is raised.

Resolve the IndentationError exception by deleting the unnecessary indentation in line 1.

script

Save and re-run the script to confirm the script executes normally.

linux$ python divide.py
Enter a number you want to divide: 10
Enter the number of times you want to divide the previous number by: 2
10 divided by 2 is: 5.0

It is important to note that SyntaxError and IndentationError exceptions are due to programming mistakes made by the developer of the script. The developer needs to resolve the issues to clear the exceptions before the script can be run by a user.

Our script is now running. However, exceptions can still occur if the user accidentally misuses the script. Let’s pretend we are a user of the script. Run the script and “accidentally” try to divide letters instead of numbers. An exception should be raised. Carefully analyze the output of the exception. Attempt to answer these questions:

linux$ python divide.py
Enter a number you want to divide: a
Traceback (most recent call last):
  File "/home/expert/Documents/divide.py", line 1, in <module>
    num1 = int(input( 'Enter a number you want to divide: '))
ValueError: invalid literal for int() with base 10: 'a'

In the previous example, we received a ValueError exception. ValueError exceptions are raised when the user inputs an invalid value. In our script, we used the int class followed by the input function, which allows the user of the script to only input integers into the script. Because we entered a letter into the script (which python interprets as a string), the ValueError exception is raised.

The exception message is invalid literal for int() with base 10: 'a', and line 1 could potentially be causing the exception to be raised.

Let’s raise a different type of exception. Run the script and accidentally divide a number by zero. Which type of exception is raised?

linux$ python divide.py
Enter a number you want to divide: 5
Enter the number of times you want to divide the previous number by: 0
Traceback (most recent call last):
  File "/home/expert/Documents/divide.py", line 4, in <module>
    print(f'{num1} divided by {num2} is: {num1 / num2}')
ZeroDivisionError: division by zero

In the previous example, we received a ZeroDivisionError exception. ZeroDivisionError exceptions are raised when the user attempts to divide a number by zero.

Let’s test your knowledge. Copy the code below into a file, and save the file as numbers.py.

numbers = [1, 2, 9, 17, 29]
print(numbers[0])
print(numbers[100])

Run the script. Answer the following questions:

  1. Which type of exception is raised?

  2. What is the message in the exception?

  3. What is the offending line of code?

  4. What can you do to resolve the exception?

The solution will be displayed on the next page.

When attempting to run the number.py script, you should see the following:

linux$ python numbers.py
1
Traceback (most recent call last):
  File "/home/expert/Documents/numbers.py", line 4, in <module>
    print(values[100])
IndexError: list index out of range

Answers to the questions are below:

  1. Which type of exception is raised?

    IndexError exception

  2. What is the message in the exception?

    list index out of range

  3. What is the offending line of code?

    line 4

  4. What can you do to resolve the exception?

    Change the print function to use a valid index ID used within the numbers list.

    In the script, there is a list named numbers that has five different values (1, 2, 9, 17, and 29). Each value in a list is referred to as an element. Each element in a list gets a unique index ID that represents the element’s position in the list. The first element gets an index ID of 0, the next element gets an index ID of 1, and so on and so forth. We have five different elements in our list, index IDs 0 to 4 are used for each value within our list. The script is raising an exception in line 4 because we are attempting to print out an element with an index ID of 100 within the numbers list. An element with index ID 100 does not exist in our list, and thus an exception is raised. To resolve the exception, we should change the print function to reference a valid index ID within the list. Change line 4 to print out index ID 1 within the numbers list.

script

Save and re-run your script to confirm it is running as expected.

linux$ python numbers.py
1
2

Let’s test your knowledge again. Copy the code below into a file, and save the file as year_born.py.

year_born = {'Rebecca Black': 1997, 'Uncle Bob': 1980, 'Alfred Yankovic': 1959}

print(year_born['Robert Van Winkle'])

Run the script. Answer the following questions:

  1. Which type of exception is raised?

  2. What is the message in the exception?

  3. What is the offending line?

  4. What can you do to resolve the exception?

The solution will be displayed on the next page.

When attempting to run the year_born.py script, you should see the following:

linux$ python year_born.py
Traceback (most recent call last):
  File "/home/expert/Documents/year_born.py", line 3, in <module>
    print(year_born['Robert Van Winkle'])
KeyError: 'Robert Van Winkle'

Answers to the questions are below:

  1. Which type of exception is raised?

    KeyError exception

  2. What is the message in the exception?

    'Robert Van Winkle'

  3. What is the offending line?

    line 3

  4. What can you do to resolve the exception?

    Either change the print function in line 3 to print a valid key or add the Robert Van Winkle key to the year_born dictionary in line 1.

    The year_born dictionary has three different key value pairs. The three keys are Rebecca Black, Uncle Bob, and Alfred Yankovic. Each key has a corresponding value that represents which year that person was born. In line 3, we attempted to print out the Robert Van Winkle key from the dictionary. However, because there is no Robert Van Winkle key in the dictionary, a KeyError exception is raised.

Let’s go ahead and add Robert Van Winkle and the year he was born to our dictionary.

script

Save and re-run your script to confirm it is executing as expected.

linux$ python year_born.py
1967

Referring back to our previous divide.py script, we determined users of the script can misuse the script and receive exceptions. In the example below, the user accidentally typed a letter instead of a number. This misuse of the program creates a ValueError exception.

linux$ python divide.py
Enter a number you want to divide: a
Traceback (most recent call last):
  File "/home/expert/Documents/divide.py", line 1, in <module>
    num1 = int(input( 'Enter a number you want to divide: '))
ValueError: invalid literal for int() with base 10: 'a'

Imagine being a user of an application, and suddenly the application stops working and raises an exception. The user, who may not be a programmer, would be confused and intimidated by the raised exception. This would lead to a poor user experience. Instead, a well-designed application or script should print a user-friendly message to the user. This will make your script more reliable, easier to work with, and will result in an overall better user experience.

To prevent exceptions from being raised and displayed to the user, you should use exception handling. Exception handling enables your script to detect exceptions. Once an exception is detected, the script will then print out a user-friendly message instead of displaying an intimidating exception.

Exception handling can be done by using a try and except block in your script. To implement this block, you can use the try and except statements.

Update your divide.py script to include a try statement (line 1 below). Underneath the try statement is where you place the code that might create an exception (lines 3 to 6 below) . The code within the try statement must be indented to the right. We are essentially “trying” to run the code within the try statement, although we are telling Python that an exception might occur.

script

To complete our try and except block, we need to implement an except statement. The block of code within the except statement will be executed only if there is an exception when running the block of code within the try statement. Update your script to include the except statement, then within the except statement, write code that will print out a user-friendly error if an exception occurs.

script

Let’s try running the script while putting in valid numbers to divide by.

linux$ python divide.py
Enter a number you want to divide: 10
Enter the number of times you want to divide the previous number by: 2
10 divided by 2 is: 5.0

Because there is no exception when running the code within the try statement, the scripts executes successfully.

Let’s now raise an exception. Run the script and divide by letters instead of numbers.

linux$ python divide.py
Enter a number you want to divide: a
Sorry you have received a general error. Please email unclebob@unclebob.com for support

Instead of a ValueError exception being shown to the user of the script, a user-friendly message is printed out to the user: Sorry you have received a general error. Please email unclebob@unclebob.com for support.

To better understand the try and except block, let’s walk through the code. When running the script, the code under the try statement will “try” to be run.

script

If the code is run successfully, the program executes as expected.

linux$ python divide.py
Enter a number you want to divide: 10
Enter the number of times you want to divide the previous number by: 2
10 divided by 2 is: 5.0

If an exception is raised in the code within the try statement, then the code within the except statement is run, and a user-friendly message is printed out.

script

Let’s try to create a ZeroDivisionError exception and see what happens.

linux$ python divide.py
Enter a number you want to divide: 5
Enter the number of times you want to divide the previous number by: 0
Sorry you have received a general error. Please email unclebob@unclebob.com for support

Because another exception was raised within the code under the try statement, the code within the except statement was run, and a user-friendly message was printed out to the user.

In the previous example, we used one except statement to handle all exceptions. However, it is not best practice to use this technique. Instead, we want our script to print out different messages for different types of exceptions. Fortunately, you can create multiple except statements for different types of exceptions to accomplish this.

First, create an except statement for ValueError exceptions. In line 8, I added the except statement and specified the type of exception I want to handle (ValueError).

script

Let’s raise a ValueError exception in our script by trying to divide by a letter.

linux$ python divide.py
Enter a number you want to divide: a
You have entered an incorrect value. Make sure to enter an integer.

Notice when the ValueError exception is detected, it is then handled by our script, and a user-friendly message is printed out.

Next, create a ZeroDivisionError exception in our script by trying to divide by zero. What do you think will happen?

linux$ python divide.py
Enter a number you want to divide: 5
Enter the number of times you want to divide the previous number by: 0
Traceback (most recent call last):
  File "/home/expert/Documents/divide.py", line 6, in <module>
    print(f'{num1} divided by {num2} is: {num1 / num2}')
ZeroDivisionError: division by zero

Oh no! We are getting another scary and intimidating exception. Why is this occurring? Remember, we only implemented exception handling for ValueError exceptions.

Let’s now implement another except statement for ZeroDivisionError exceptions.

script

Save the script. Run the script and try to divide by zero.

linux$ python divide.py
Enter a number you want to divide: 5
Enter the number of times you want to divide the previous number by: 0
Sorry you cannot divide by zero.

The ZeroDivisionError exception should be detected and then handled by our script. A user-friendly message is printed out.

You now have a script that works and is able to handle several different types of exceptions. Great job!

Let’s put your newly learned exception handling skills to the test! Below is a script that you can run against the Cisco Always-On IOS XE Sandbox. The Always-On IOS XE Sandbox is essentially a virtual IOS router that is always online that you can send API calls to.

Copy the code below into a file, and save the file as get_interface.py.

import json
import requests
from urllib3.exceptions import InsecureRequestWarning

requests.packages.urllib3.disable_warnings(category=InsecureRequestWarning)

# the variables below assume the user is leveraging the
# always on sandbox.

# user credentials for your IOS-XE device
username = 'admin'
password = 'C1sco12345'

# header sent in API call to request data in JSON encoding
headers = {'Accept': 'application/yang-data+json'}

# user will input an integer to specify the GigabitEthernet interface # they want to retrieve configuration for
name = int(input('Please enter the ID of the GigabitEthernet interface for which you want to retrieve configuration: '))

# url to send API call to
url = f'https://sandbox-iosxe-latest-1.cisco.com/restconf/data/ietf-interfaces:interfaces/interface=GigabitEthernet{name}'

# send API call using requests
response = requests.get(url, auth= (username, password), headers=headers, verify=False)

# convert response to dictionary
data = response.json()

# convert dictionary back to JSON object and print JSON with indentations
print(json.dumps(data, indent=4))

Let’s confirm the script is working as expected.

When using the script, specify the ID of GigabitEthernet interface for which you want to retrieve configuration. For example, enter ‘1’ to get the configuration for interface GigabitEthernet1.

(main) linux$ python get_interface.py
Please enter the ID of the GigabitEthernet interface for which you want to retrieve configuration: 1
{
    "ietf-interfaces:interface": [
        {
            "name": "GigabitEthernet1",
            "description": "MANAGEMENT INTERFACE - DON'T TOUCH ME",
            "type": "iana-if-type:ethernetCsmacd",
            "enabled": true,
            "ietf-ip:ipv4": {
                "address": [
                    {
                        "ip": "10.10.20.48",
                        "netmask": "255.255.255.0"
                    }
                ]
            },
            "ietf-ip:ipv6": {}
        }
    ]
}

Enter ‘2’ to get the configuration for interface GigabitEthernet2.

(main) linux$ python get_interface.py
Please enter the ID of the GigabitEthernet interface for which you want to retrieve configuration: 2
{
    "ietf-interfaces:interface": [
        {
            "name": "GigabitEthernet2",
            "description": "Network Interface",
            "type": "iana-if-type:ethernetCsmacd",
            "enabled": false,
            "ietf-ip:ipv4": {},
            "ietf-ip:ipv6": {}
        }
    ]
}

Now that your script is working as expected, attempt the following:

  1. How can you break the script from a user perspective? Which exceptions can you raise?

  2. Use a try/except block within the script to handle any possible exceptions users might experience.

Do this on your own and have fun! The solution will be displayed on the next page.

Your solution might look different than the one given below, and that’s OK.

  1. How can you break the script from a user perspective? Which exceptions can you raise?

The example raised a ValueError exception by inputting a non-integer value into the script.

(main) linux$ python get_interface.py
Please enter the ID of the GigabitEthernet interface for which you want to retrieve configuration: a
Traceback (most recent call last):
  File "/home/expert/Documents/get_interface.py", line 18, in <module>
    name = int(input('Please enter the ID of the GigabitEthernet interface for which you want to retrieve configuration: '))
ValueError: invalid literal for int() with base 10: 'a'

The example raised a KeyboardInterrupt exception by pressing CTRL+C while the script was running.

(main) linux$ python get_interface.py
Please enter the ID of the GigabitEthernet interface for which you want to retrieve configuration: ^CTraceback (most recent call last):
  File "/home/expert/Documents/get_interface.py", line 18, in <module>
    name = int(input('Please enter the ID of the GigabitEthernet interface for which you want to retrieve configuration: '))
KeyboardInterrupt
  1. Use a try/except block within the script to handle any possible exceptions users might experience.

To handle those two exceptions, the following was done:

script

Let’s make sure the script is able to handle ValueError exceptions.

(main) linux$ python get_interface.py
Please enter the ID of the GigabitEthernet interface for which you want to retrieve configuration: a
 You must enter a number when specifying an interface.

Let’s also make sure the script is able to handle KeyboardInterrupt exceptions.

(main) linux$ python get_interface.py
Please enter the ID of the GigabitEthernet interface for which you want to retrieve configuration: ^C
You exited the script by pressing CTRL+C

Finally, let’s confirm the script still runs as expected when entering an integer.

Please enter the ID of the GigabitEthernet interface for which you want to retrieve configuration: 1
{
    "ietf-interfaces:interface": [
        {
            "name": "GigabitEthernet1",
            "description": "MANAGEMENT INTERFACE - DON'T TOUCH ME",
            "type": "iana-if-type:ethernetCsmacd",
            "enabled": true,
            "ietf-ip:ipv4": {
                "address": [
                    {
                        "ip": "10.10.20.48",
                        "netmask": "255.255.255.0"
                    }
                ]
            },
            "ietf-ip:ipv6": {}
        }
    ]
}

Nice work! You now understand what exceptions are and how to handle them within your Python code.

Other Learning Options