Exceptions in Python: Everything You Need To Know!

In this article let us learn about “Exceptions” in Python and the various ways of handling them.

The craftsmanship of a programmer can be measured by the degree of robustness of the code they write. To be an excellent python craftsman, we need to learn everything we can about Exceptions.

Let us start by understanding what Exceptions are.

What are Exceptions in Python

Exceptions are mechanisms provided to us by the Python Programming Language that can be used to deal with errors that occur during the execution of programs.

To understand what I mean by “errors that occur during the execution of programs“, let us take a look at the 2 types of errors we can see while writing code in python. They include

  1. Syntax errors and
  2. Exceptions

Syntax errors are those that you come across if you are not following the rules of any given programming language, and this is true for python too, except python likes to call them using the name “parser errors”

Here is a very simple example of Syntax Errors:

print ('Hello! Marvel Universe)

If you run the above code, you will get an error that looks something like this

File "", line 1
     print ('Hello! Marvel Universe)
                                    ^
 SyntaxError: EOL while scanning string literal

(Do try out the code as you read the article, that is by far the best way to learn. Not kidding, startup Python and start coding!)

As you can see, I have forgotten to close the string with the single quotes at the end, this confuses the python parser and we end up getting a error message (“Hey you forgot to close the string” would have been a better error message parser!)

These syntax errors (a.k.a parser errors) are caught by the python parser and you will not be able to run your program before you correct these errors.

Syntax errors are common among beginners, and as the programmer gets more and more experienced using the language, the lesser the syntax mistakes they make. Now that we have learned what syntax errors are, let us go ahead and switch our focus to the main topic of this article, which are Exceptions.

Exceptions as we have seen above, occur at runtime. i.e. if something unexpected happens as your program is running, your program crashes with an error message. These errors are called Exception errors!

Consider the code snippet below.

open('End Game:Part 3', 'r')

Here I am trying to open a file named ‘End Game: Part-3’ for reading.

When I try to run this code, I get the following Error message

Traceback (most recent call last):
 File "", line 1, in 
     open('End Game:Part 3', 'r')
 FileNotFoundError: [Errno 2] No such file or directory: 'End Game:Part 3'

The error message basically means that the Python Interpreter is not able to find the requested file.

But then, why the name exceptions?

The program will run fine except for a few situations and these few “excepts” are called exceptions!

Like in the example above, this code will run fine “except” when the file is not present in your computer.

In the code snippet above, we can just make sure that the file exists, as we control the computer on which the code runs.

But then, say you give the program to your friend, as he/she wants to see your cool program. If he/she does not have the file, then they will be greeted with the Python Interpreter’s terrifying error messages and they will think you broke their computer!

If the file your code wishes to open is not present, the Python Interpreter does not have any other option but to stop the execution of the program with an error message, giving us clues of why the program was not able to run.

Hence another way of looking at Exceptions is

Exceptions are controlled program crash by the interpreter

Paul Barry, Head first Python, O’Reilly

To avoid these crashes, we need to learn how to deal with these exceptions/ “bad situations” when they occur while our program is run.

How to use try and except clauses to catch Exceptions

Code which is problematic and has the potential for runtime errors is placed inside the try clause

In our above example of trying to open a file, we can write the try clause like this

try:
     f = open('End Game:Part 3', 'r')
     for line in f.readlines():
         print(line)

Here in the code above, we are opening the file named ‘End Game:Part 3‘ and then printing the lines one by one using a for loop.

In case there is an run time error, a.k.a an exception, while trying to open the file (inside the try clause) then the python interpreter takes the execution to the except clause.

An except clause for the above try clause will look something like this

except FileNotFoundError:
    print('Sorry Bro! Movie not out yet!')

This “FileNotFoundError” is the type of error which this except clause is meant to catch.

If you put the code together and run this code now, then instead of a terrifying error message, you will now see something like this

try:
     f = open('End Game:Part 3', 'r')
     for line in f.readlines():
         print(line)
 except FileNotFoundError:
     print('Sorry Bro! Movie not out yet!')

 Sorry Bro! Movie not out yet!

Now seeing this friendlier message, your buddy who wanted to execute your program, won’t start crying over how you broke his computer!

So in short

The except clause is used for dealing with exceptions once they are caught.

This example showed you how to catch an exception and print out a friendly error message.

In this particular example, there is nothing much you can do about the exception, either the file is either present or it is not there.

But, in real world situations, when you write code professionally, you might not want to end the program on the first ever exception that comes in your way, you would probably want to do something about it, so that your program can keep running.

The next section covers 3 things you can do when an exception occurs.

3 ways to handle Exceptions

After catching an error, the next step is to sit quietly and think for a bit and come up with a decision about what we are going to do about the exception!

There are 3 ways to deal with exceptions

  1. Ignore the exception and move on to execute the rest of the program, hoping nothing bad will happen. (very bad practice! your program will most probably crash at some point, or worse, will lead to very unexpected results!)
  2. Retry the problematic code till the error goes away and then continue the rest of the program. (better alternative, but not possible in all situations!)
  3. If it is not possible to replace the code then log the error and terminate the program after some cleanup, then come back later and read the logs and correct the mistakes made! (you will probably need to do this most of the time you run into exceptions!)

Let us take a look at some examples of the above 3 Strategies and see how that works out!

Strategy#1: Ignore Exceptions

Take a look at the code below.

print ('Time to Add 2 Numbers!!')
numbers_to_add = []
for i in range(1, 3):
         x = int (input('Enter number ' + str(i) + ': '))
         numbers_to_add.append(x)
print('The sum of 2 numbers you have entered is ', numbers_to_add[0] + numbers_to_add[1])

Here we simply get 2 numbers from the user, convert them to integers, add them up and print out the result.

You might be thinking what could go wrong in a simple program like this. Let’s run this code and see what happens.

Time to Add 2 Numbers!!
Enter number 1: 10
Enter number 2: 200
The sum of 2 numbers you have entered is  210

Pretty straightforward right!

How I wish it to be straightforward too! But, Alas! Our users are full of cool imagination!

Consider the situation below

Time to Add 2 Numbers!!
Enter number 1: 100
Enter number 2: apple
Traceback (most recent call last):
File "", line 5, in 
     x = int (input('Enter number ' + str(i) + ': '))
ValueError: invalid literal for int() with base 10: 'apple'

Here our imaginative user, decided to give apple in place of the second number and crashed our beautiful code!

ValueError is the name of the error where the Python Interpreter expects to see an object of one type, but instead sees an object of another type. In our case, instead of an integer the interpreter sees a float!

Let us try to follow Strategy#1 and see if we can ignore this exception not let our program crash.

The first thing we need to do is to put the problematic code inside the try clause.

try:
    x = int (input('Enter a number: '))
         numbers_to_add.append(x)

Next we need to catch the exception in the except clause

except ValueError:
    numbers_to_add.append(0)

Here if our user gets imaginative and tries to enter something randomly, we catch that and make the value as 0 instead!

The entire code looks something like this

print ('Time to Add 2 Numbers!!')
numbers_to_add = []
for i in range(1, 3):
     try:
         x = int (input('Enter number ' + str(i) + ': '))
         numbers_to_add.append(x)
     except ValueError:
         print('You did not enter number' + str(i) + '!')
         numbers_to_add.append(0)
print('The sum of 2 numbers you have entered is ', numbers_to_add[0] + numbers_to_add[1])

Let us give our code to the imaginative user again! Try crashing my code now!

Time to Add 2 Numbers!!
Enter number 1: 100
Enter number 2: orange
You did not enter number 2!
The sum of 2 numbers you have entered is  100

That is cool, we did not let the user crash our code!

For a simple code like this, our strategy#1 works okay,but this is not the perfect approach

Consider a not so mischievous user who would enter values like this

Time to Add 2 Numbers!!
Enter number 1: ten
You did not enter number1!
Enter number 2: seven
You did not enter number2!
The sum of 2 numbers you have entered is  0

Now though this user actually entered 2 numbers (yes, in string format, but they are numbers by meaning!) we have given him a wrong answer!

So substituting default values and just continuing with the execution is a bad idea and will lead to incorrect results.

Either a crashed program or a program that gave bad results will not help your reputation as the Spock in your team!

So what to do with this code then? Let’s try Strategy#2!

Strategy#2: Retry the problematic code till the error goes away

So in the case of our adding numbers example we can do something like this!

print ('Time to Add 2 Numbers!!')
numbers_to_add = []
for i in range(1, 3):
     while True:
         try:
             x = int (input('Enter number ' + str(i) + ': '))
             numbers_to_add.append(x)
             break;
         except ValueError:
             print('Enter a number using a combination of the characters below:')
             print('1,2,3,4,5,6,7,8,9,0')
print('The sum of 2 numbers you have entered is ', numbers_to_add[0] + numbers_to_add[1])

Let try running this code again!

Time to Add 2 Numbers!!
Enter number 1: one
Enter a number using a combination of the characters below:
1,2,3,4,5,6,7,8,9,0
Enter number 1: 100
Enter number 2: apple
Enter a number using a combination of the characters below:
1,2,3,4,5,6,7,8,9,0
Enter number 2: 25
The sum of 2 numbers you have entered is  125

Now no matter how imaginative our mischievous users get, the code will always execute as expected.

This is an example of good robust code that neither crashes nor produces inaccurate results!

So Strategy#2 of Try and try again till you win works perfectly for our Number Party example!

But what about the End Game example?

No matter how many times we try, the file is not going to magically appear filled with the expected contents (I don’t know about you, but I was expecting a file with the entire story of the 3rd part of End game where Iron man comes back to lead the team again!)

So there is only one thing we can do at this point

Strategy#3: Log the error and Terminate the program cleanly

Since we cannot fix the error in this scenario, we are going to do the next best thing.

try:
     f = open('End Game:Part 3', 'r')
     for line in f.readlines():
         print(line)
 except FileNotFoundError:
     log = open('log.txt', 'w')
     log.write('FileNotFoundError Exception has occured')
     log.close()
     print('An Error has occurred during the execution of this program')
     print('Please send the file named "log.txt" to admin@embeddedinventor.com')

Here a log file is created and the user is asked to send the log file via email to you!

Running this code will result in something like this

An Error has occurred during the execution of this program
Please send the file named "log" to admin@embeddedinventor.com

So as you can see, when nothing can be done about an Exception, the best thing we can do is to log the error!

So far we have seen problematic code which can only produce 1 type of exception and we have handled those exceptions nicely via except clauses.

But what if one line of code can produce say 3 different exceptions (depending on the situation) and we would like to deal with each of these 3 exceptions separately?

The next section explains how this can be done!

How to handle a try block that produce more than 1 type of exception?

Yes, you read that right, it is possible for some lines of code to produce more than one exception. Let us see an example of such a code.

 print('Time for a Division Party!') 
 X = int(input('Enter the Numerator: '))
 Y = int(input('Enter the Denominator: '))
 Z = X/Y
 print (str(X) + '/' + str(Y) + ' = ' + str(Z)) 

The code above takes 2 numbers as inputs from the user, divides the first number by the second one and prints out the result.

This simple code is capable of producing 2 exception, a.k.a capable of crashing in 2 different situations. Can you guess what they are?

As you might have guessed the 1st exception is our mischievous user entering some random text instead of numbers. Just for fun let’s give tomato as an input!

 Time for a Division Party!
 Enter the Numerator: 100
 Enter the Denominator: tomatoes
 Traceback (most recent call last):
 File "", line 3, in 
     Y = int(input('Enter the Denominator: '))
 ValueError: invalid literal for int() with base 10: 'tomatoes'

Here we got ValueError just like before.

The second exception is the fact that it is illegal to divide by 0 (zero) in math, which will result in your program crashing as shown below.

Time for a Division Party!
Enter the Numerator: 24
Enter the Denominator: 0
Traceback (most recent call last):
File "", line 4, in 
    Z = X/Y
ZeroDivisionError: division by zero

Here the Exception is aptly named as ZeroDivisionError.

So now how can we make sure we catch both these errors?

The answer is simple! Using 2 except clauses! One to catch the ValueError and the other to catch the ZeroDivisionError as shown in the code snippet below.

print('Time for a Division Party!') 
while True:
     try:
           X = int(input('Enter the Numerator: '))
           Y = int(input('Enter the Denominator: '))
           Z = X/Y
           print (str(X) + '/' + str(Y) + ' = ' + str(Z)) 
           break;
     except ValueError:     
     print('Please cut the crap and enter valid numbers!') 
     except ZeroDivisionError:
     print ('Illegal in Math to Divide by Zero!')
     print ("Don't enter 0 as denominator, let's try again!")

(This syntax is similar to the switch-case statements in C)

Let us see how the output will look like now!

 Time for a Division Party!
 Enter the Numerator: 100
 Enter the Denominator: tomatoes
 Please cut the crap and enter valid numbers!
 Enter the Numerator: 100
 Enter the Denominator: 0
 Illegal in Math to Divide by Zero!
 Don't enter 0 as denominator, let's try again!
 Enter the Numerator: 100
 Enter the Denominator: 10
 100/10 = 10.0

As you can see, now our code is more robust/ hard to break and hence has a very high degree of craftsmanship!

But what to do when a few lines of code can cause say 5 or 10 or more Exceptions?

Should we write an except clause for each of these 10 exceptions? There is a better way to do this! The next section shows you the 2 ways to catch multiple exceptions from a single except clause.

2 ways to catch multiple exceptions in a single except clause

Those 2 strategies are

  1. Using a common base Exception class
  2. Using except statement without any arguments

Let us see these strategies in a bit more detail.

Strategy#1: Using a common base Exception class

Exceptions are objects just like everything else in python. Exceptions form a hierarchy of classes to represent the various types of errors you can come across in coding.

https://docs.python.org/2/library/exceptions.html#exception-hierarchy

Have a look at the screenshot above, the ZeroDivisionError we saw in the Division party! example is a class derived from the ArithmeticError class, which in-turn is derived from the StandardError class, which in-turn in derived in from the Exception and this in-turn is derived from the BaseException class. So

BaseException -> StandardError -> ArithmeticError -> ZeroDivisionError

Also if you notice, there are 3 Exceptions under the ArithmeticError class:

  • FloatingPointError,
  • OverFlowError and
  • ZeroDivisionError

Say you wish to catch all 3 exceptions together in a single except class then you will write an except clause like this

except ArithmeticError as err:
    print('An arithmetic error has occured: ' + str(err))

Here the exception you have just caught is stored into an object named err of type ArithmeticError.

Let us see what kind of output we get if we use this except clause in pur Division Party example.

print('Time for a Division Party!') 
while True:
     try:
         X = int(input('Enter the Numerator: '))
         Y = int(input('Enter the Denominator: '))
         Z = X/Y
         print (str(X) + '/' + str(Y) + ' = ' + str(Z)) 
         break;
     except ValueError:
     print('Please cut the crap and enter valid numbers!') 
     except ArithmeticError as err:
     print('An arithmetic error has occured: ' + str(err))

If you run this code, you will get something like this!

Time for a Division Party!
 Enter the Numerator: 10
 Enter the Denominator: 0
 An arithmetic error has occured: division by zero

Every built-in exception type has a friendly string which can be retrieved using str() function. In this example, str(err) gives us the string “division by zero” which is very helpful indeed!

Strategy#2: Using except statement without any arguments

There is another (not so recommended) way of doing what we did with Strategy#1, which is to use except clause with no arguments as shown below.

try:
     X = int(input('Enter the Numerator: '))
     Y = int(input('Enter the Denominator: '))
     Z = X/Y
     print (str(X) + '/' + str(Y) + ' = ' + str(Z)) 
except:
    print('An Exception has occurred!')
     raise

This code’s output will look something like this

Enter the Numerator: 90
Enter the Denominator: banana
An Exception has occured!

This is not a professional way of doing things, as the user does not get much information to go with correcting the mistakes he made.

What happens here is that the Python Interpreter simply “except:” with “except BaseException” and BaseException as we saw above is the class from which all other Exceptions are derived from, in other words, BaseException is the great-grand-dad of all Exceptions!

These are useful in a very particular scenario, when you wish to log the error and pass the exception to the calling code. The statement raise in python does exactly that!

That is an advanced topic, which deserves its own article! (I will update the link once the article is published)

So in short, if you are a beginner, go with Strategy#1 and not Strategy#2!

Try Except Clauses make the code hard to read yes, but as you mature as a python programmer, you will learn to spot the important portion of code (what’s inside the try clause) and ignore the rest of the code (and only look at the except clauses when you really need to!)

If you made it till here then congratulations to you! I am sure you will be an excellent Python Craftsman in the near future!

The concepts presented in this article provide the 25% of knowledge you will need 75% of the situations you will be working with Exceptions.

There are some rarely used concepts which I have not presented here, I will update this article with links to those articles once they are ready! So be sure to check this section of the article for updates!

In case you are in a hurry to learn them and you are in the mood to do some googling, those concepts are the following

  • usage of else and finally clauses with try/except clauses and their order of Execution
  • user defined custom Exceptions
  • Exception Chaining and
  • Manually raising Exception with raise

So, I will stop here. If you ever run into an Exception, consider yourself lucky, as now you have the opportunity to correct the error BEFORE YOU HAVE DELIVERED THE PROGRAM TO THE USER!

I hope you learned something useful and got some value from this article!

Feel free to share this article with your friends and colleagues!