Beginner’s Guide to Exceptions in Python

Catch errors and handle them in your app.

Martin McBride
Apr 21 · 9 min read
Photo by Sigmund on Unsplash

In this article we will look at exceptions:

  • Program errors.

This article is taken from my book Python Quick Start.

Program errors

Computer programs sometimes go wrong. There are three broad types of error you will encounter when you are writing code:

  • Syntax errors.

Syntax errors occur when the code you type in isn’t valid. This is often due to typing errors or misunderstanding Python syntax. For example:

s = [1, 2, 3           # Missing end bracket
1a = 3 # Variable name can't start with a number
foor i in range(10): # Misspelling for
do_something()
do_something_else() # Wrong indentation

This code is full of mistakes, and Python simply won’t run it until you fix things. These errors are usually the easiest to find and fix because Python highlights them.

Logical errors are where you have typed invalid Python code, but the code you typed in doesn’t do what you thought it would. For example:

x = 10
if x <= 10:
print('x is less than 10')

The problem here is that the code is valid but logically incorrect. The programmer wanted the message to only be displayed is x was less than ten, but the code actually checks if x is less than or equal to ten. These types of error can be more difficult to spot because they often only happen in specific circumstances (the code above works for any number except 10). You can only really eliminate these bugs by testing your code thoroughly. But the good news is, once you have spotted the bug it happens every time (the code will always go wrong for the value 10), which makes it easier to find.

Runtime errors are things that go wrong because of external factors. For example:

  • If your program saves a file to disk, it will fail if the disk is full.

You can write extra code to check for these things, but you can’t catch everything. For example, your program might check that the network is connected before it tries to read some data, but what happens if someone unplugs the cable while the data is transferring? This is where exceptions come in.

What are exceptions

Here is an example of exceptions in action:

age = int(input('How old are you?'))
print('You are', age, 'years old')

This code works fine provided the user types in a number. But if they type in something else, such as “hello”, the program terminates with a console message:

Traceback (most recent call last):
File "test.py", line 1, in <module>
age = int(input('How old are you?'))
ValueError: invalid literal for int() with base 10: 'hello'

The problem here is that the int function is being handed a value 'hello'. Since int cannot convert this string into a number, it can't return a sensible value. So the int function doesn't return in the normal way at all. Instead, it raises an exception. This exception causes Python to abandon the normal running of the program, and jump right out of the program back to the console.

This type of behaviour is called raising an exception because it only happens in exceptional circumstances (in this case, when the user provides bad input). It is also sometimes called throwing an exception, which means the same thing.

When an exception is thrown, Python also provides an Exception object that gives more information about the causes of the error.

The console detects that the program has raised an exception, and displays the message above. The information in the error message comes from the Exception object.

One final aspect of exceptions that is incredibly useful is that you can catch an exception in your own code. This allows your code to check the Exception object, deal with the problem, and carry on running. Here is an example:

try:
age = int(input('How old are you?'))
print('You are', age, 'years old')
except:
print('Invalid age value')
age = None

We will explain this in more detail below, but basically, we have placed our main code in a try block, and our error handling code in an except block (the syntax is similar to if and else). The way this works is that if an exception is raised in the try block, it is caught and causes the except block to run, which in this case simply prints a message and sets the age to None. The program then continues as before.

In no exceptions occur within the try block, the except block is completely skipped.

Exception types

There are dozens of built-in exceptions, we will look at some of the common ones here.

ImportError

This type of exception is thrown if your code tries to import something that doesn’t exist:

from math import sqrt # This is ok, math has a sqrt function
from math import xyz # ImportError, math has no xyz function

IndexError

This type of exception is thrown if your code tries to access an out of range list element:

k = [1, 2, 3, 4] # k has 4 elements
k[1] # This is ok, element 1 exists
k[6] # IndexError, there is no element 6, the list is only 4 long

TypeError

This type of exception is thrown if as operation fails because of the type of data:

len('abc')   # This is ok, the length of the string is 3.
len(10) # TypeError, you can't find the length of an integer

ValueError

This type of exception is thrown if an operation fails because of an invalid value, for example:

a = int('1')      # This is fine, '1' can be coverted to and integer.
a = int('hello') # ValueError, 'hello' is not a number

ZeroDivisionError

This type of exception is thrown if you try to divide a number by zero, for example:

a = 1/0   # ZeroDivisionError

Catching exceptions

We have already seen how to catch an exception. We will now look at this in a bit more detail.

Here is a program where we ask the user for a number, and display the corresponding day of the week:

days = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']
day_no = int(input('Enter a day number 0-6 '))
day = days[day_no]
print('The day is', day)

Clearly, we have the same problem as with our earlier age program — the user could enter an invalid string. We can use the same solution:

try:
days = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']
day_no = int(input('Enter a day number 0-6 '))
day = days[day_no]
except:
print('Invalid day number')
day = None
if day:
print('The day is', day)

Only catch exceptions you can handle

The code above will catch any exception that gets thrown when the code in the try block runs.

Generally, it is better to only catch the exceptions that you are intending to handle. If a completely different exception occurs, that our software knows nothing about, it is usually best to let it go. Some other part of the system might be set up to handle that exception properly, it is best to allow that to happen.

We can do that by specifically catching a particular exception, for example, we would expect a ValueError if the user typed in an invalid string:

try:
days = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']
day_no = int(input('Enter a day number 0-6 '))
day = days[day_no]
except ValueError:
print('Invalid day number')
day = None
if day:
print('The day is', day)

So now our code will handle a ValueError and carry on working.

But if some other type of exception occurs, for example, a network error, our code can’t do anything about it. We ignore any unwanted exceptions and hopefully the code that called our code will handle it.

However, we do need to make sure we handle all the exceptions we can. In the code above, what if the user typed in '8'? The int function would convert the string into an integer, but days[day_no] would throw an IndexError because there are only 7 days in the days list. We actually need to check for both types of error. We can do it like this:

try:
days = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']
day_no = int(input('Enter a day number 0-6 '))
day = days[day_no]
except (ValueError, IndexError):
print('Invalid day number')
day = None
if day:
print('The day is', day)

In this case, we are using a tuple of exception types (ValueError, IndexError) and the except clause applies to any type in that tuple. You need to put brackets around the tuple.

Alternatively, we can have different except clauses for each type, like this:

try:
days = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']
day_no = int(input('Enter a day number 0-6 '))
day = days[day_no]
except ValueError:
print('Invalid number string')
day = None
except IndexError:
print('Day number must be 0 to 6')
day = None
if day:
print('The day is', day)

This allows us to handle the two exceptions differently.

Accessing the exception message

Exceptions often contain additional information about what went wrong. You can access the exception within the except block like this:

try:
days = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']
day_no = int(input('Enter a day number 0-6 '))
day = days[day_no]
except ValueError as e:
print(e)
day = None
except IndexError as e:
print(e)
day = None
if day:
print('The day is', day)

Using as makes the exception object available to our code in the variable e.

This time, instead of printing a custom message, we print the exception object itself.

You will sometimes want to do both — you can display a helpful, custom message to explain what has gone wrong to the user, and also display the error content to help debug the problem.

Using else with exceptions

We can add an else clause to the end of our try statement, like this:

try:
days = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']
day_no = int(input('Enter a day number 0-6 '))
day = days[day_no]
except ValueError:
print('Invalid number string')
except IndexError:
print('Day number must be 0 to 6')
else:
print('The day is', day)

The else clause only gets called if no exception occurs. In this case, we are using it to print the result.

In all cases, the code executes exactly one of the clauses — either a one except clause or the else clause.

Using finally with exceptions

If you add a finally block to a try statement, it will always get executed at the end, no matter what:

try:
days = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']
day_no = int(input('Enter a day number 0-6 '))
day = days[day_no]
except ValueError:
print('Invalid number string')
except IndexError:
print('Day number must be 0 to 6')
else:
print('The day is', day)
finally:
print('All done!')

The finally clause always runs:

  • If an exception occurs and is caught, the matching except clause runs, followed by the finally clause.

Although we have used finally here to print a message, it is usually used for "tidying up" type task, such as closing any files that the program might have been using. This ensures that the program always gets a chance to do what it needs to do, even if an error occurs.

Throwing exceptions

Consider this code:

def divide(a, b):
return a/b
print(divide(3, 2))
print(divide(3, 0))

In the first print statement, divide(3, 2) returns 1.5, which is printed. In the second print statement, divide(3, 0) throws a divide by zero error.

Our code could check the value of b for zero, then raise a different exception that provides more specific information about the problem:

def divide(a, b):
if b == 0:
raise ValueError('b cannot be zero')
return a/b
print(divide(3, 2))
print(divide(3, 0))

This time if b is zero, the code raises a ValueError with a message saying that b cannot be zero.

You can also catch and re-raise exceptions:

def divide(a, b):
try:
return a/b
except ZeroDivisionError:
raise ValueError('b cannot be zero')
print(divide(3, 2))
print(divide(3, 0))

In this case, rather than checking for b being zero, we just calculate a/b. If b is zero, this will throw a ZeroDivisionError exception. We then catch this exception and throw a ValueError exception. This allows us to swap one exception for another.

Summary

Programs that crash and burn at the slightest error are amateurish and unfriendly. Catching exceptions and handling them in your code is onebasic step towards making professional quality software.

Geek Culture

Proud to geek out.

Sign up for Geek Culture Hits

By Geek Culture

Subscribe to receive top 10 most read stories of Geek Culture — delivered straight into your inbox, once a week. Take a look.

By signing up, you will create a Medium account if you don’t already have one. Review our Privacy Policy for more information about our privacy practices.

Check your inbox
Medium sent you an email at to complete your subscription.

Martin McBride

Written by

I am a software developer with over 30 years experience in Java, Python and C++. I write for pythoninformer.com.

Geek Culture

A new tech publication by Start it up (https://medium.com/swlh).

Martin McBride

Written by

I am a software developer with over 30 years experience in Java, Python and C++. I write for pythoninformer.com.

Geek Culture

A new tech publication by Start it up (https://medium.com/swlh).

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store