A Complete Guide to Python Exceptions

A Python program will terminate as soon as it encounters an error.

These errors can be syntax errors or they can be exceptions. An exception is an event that interrupts the normal flow of your Python program.

This article will teach you how to handle Python exceptions using try and except statements.

Table of Contents

You can skip to a specific section of this Python exception tutorial using the table of contents below:

Introduction to Python Exceptions

There are many errors that can happen in computer programs. Broadly speaking, they can be divided into compiler errors and runtime errors.

As their names suggest, compiler errors occur at the compile-time and runtime errors occur while the program is running. Although the former can be identified, generally, runtime errors can only be found when the program throws an exception and crashes.

If the programs are to run smoothly without any interruptions, languages need to have a mechanism to address this. This is why exceptions exist!

Take a look at the following Python code to see an example of an exception.

my_list = [0, 1, 2, 3]

print(my_list[5])

If we try to execute this code, Python will throw the following error.

IndexError: list index out of range

The reason this exception is thrown is because we're trying to reference an item from our Python list that doesn't exist. my_list contains four elements, so its highest index is 4 (since Python is zero-indexed).

Had this piece of code been in a bigger program, once it reaches this point, it would throw this error and stop its execution. If this had happened in a packaged program already running in an end user’s device, this would be a disaster. The program would crash every time it encounters this error.

This is why exception handling is so important. It is an essential tool that allows programming languages to deal with runtime errors. Exceptions prevent the execution of the program from being terminated.

To see this concept in action, let's add a few lines to this code:

my_list = [0, 1, 2, 3]

try:

  print(my_list[5])

except:

  print(100)

This program would give 100 as its output.

If we take a look at the code, we can see that the original print statement is placed in a try block. There is also an except block added with a print statement.

In simple terms, this means the script will try to print the 5th index of my_list. If it does not succeed, it will print 100 instead. Note that we have not specified what types of errors should be caught.

In Python, the try block is always followed by at least one except statement. You can use multiple except define what errors you want to catch and how you want your program to respond to them. This allows your machine to handle different exceptions in different ways.

In Python, the most popular and widespread class for exception handling is the Exception class. Python developers can also create user-defined exceptions. This Exception class is usually the parent from which user-defined exception classes are derived from. The Exception class itself even has a parent class and sibling classes.

We'll consider the hierarchy of Python exceptions in the next section of this tutorial.

Python Exception Hierarchy

The hierarchy of Python exception classes is based on the BaseException class. All exception instances or objects are derived from this BaseException class or its child classes. BaseException has four immediate child classes"

  • SystemExit
  • GeneratorExit
  • KeyboardInterrupt
  • Exception

Catching Exceptions

Exceptions exist so that developers can catch them and decide how to respond to them.

If the program might potentially throw many types of errors, they can either be addressed as a whole, in categories, or even individually.

The following is a general guide with examples on how to deal with the exceptions.

Catch All

Catching all exceptions is not advised for complex applications. However, for basic Python scripts, it can be useful to react to every exception in the same way.

Here is the basic syntax for a catch all exception:

try:

   # An instruction that might likely

   # throw an exception

except:

   # What to do about it

Reacting to Specific Exceptions

This is the most common method for exception handling in Python. It allows you to react to specific

Here's the syntax you'd use to react to an exception from the Exception class. You'd use similar logic for any other child or sibling class.

try:
   # An instruction that might likely
   # throw an exception but preferably
   # not a from a sibling class of Exception
except  Exception:
   # What to do about it

Catching Multiple Exceptions

Multiple except statements can be chained together to catch different exceptions and handle them individually. The syntax is for one link is this chain is below:

except  YourErrorHere:

Here's a longer example of how you could handle longer chains of exceptions in a Python script:

try:
   # An instruction that might likely
   # throw an exception
except  BufferError  as e:
   # What to do about the caught BufferError
except  ArithmeticError  as e:
   # What to do about the caught ArithmeticError

Raising Custom Exceptions

Python offers lots of flexibility when it comes to raising exceptions. Developers can even instruct programs to raise built-in exceptions in addition to the user-defined ones. This comes handy when performing unit testing with exception handlers and specific conditions in a program.

Users can also raise warning messages in addition to exceptions. Warnings are useful in informing or altering the users about the program in scenarios where you don't want to terminate the program, such as:

  • the use of outdated modules
  • reaching allowed memory limits
  • using a method that is often used incorrectly

Tt is the sole responsibility of the user to react to these warnings. There is no mechanism to regulate what exceptions or warnings can or cannot be raised by the user.

As an example, the following code.

my_list = [0, 1, 2, 3]

my_list_index = 5

if(my_list_index>len(my_list)):

   raise  Exception("Index is out of range. Use a value less than 4")

The above code would generate:

Exception: Index is out of range. Use a value less than 4

This raise statement gives developers the ability to force exceptions at different points of their code. These exceptions must be fulfilled with an instance of BaseException, or one of the derived classes (like Exception, which I used in the example above).

User-Defined Exceptions

Python enables developers to define custom exception classes. They are usually derived from the Exception class. They behave in the same way as the other Exception-based classes do.

As a rule of thumb, exception classes should be kept as simple as possible with just enough attributes to identify the potential error to be caught. Moreover, a main base class should be created that extends the Exception class. Other subclasses should be derived from that user-defined base class depending on the error conditions.

Let's consider an example.

class  MyBaseErrorClass(Exception):
   # This is the user-defined base class
   pass

class  MyErrorOne(MyBaseErrorClass):
   # This is a user-defined child class
   # derived from the user-defined base class

   def  __init__(self, my_value):
      self.my_value = my_value

   def  __str__(self):
      return(repr(self.my_value))

class  MyErrorTwo(MyBaseErrorClass):
   def  __init__(self, my_value, my_user_name):
      self.my_value = my_value
      self.my_user_name = my_user_name

def  __str__(self):
   return('user ' + self.my_user_name + ' entered ' + str(self.my_value))

We can test what happens if the error gets caught as follows.

raise(MyErrorTwo(2,'David'))

This generates:

MyErrorTwo: user David entered 2

Python Exception Wrapping

In exception wrapping, wrapper classes are written for exceptions to wrap one exception in another. This comes in handy when there are collaborative implementations of libraries.

For instance, consider a scenario in which a file reader library internally uses another 3rd party library called SomeLib to acquire files from Google Drive. In case the expected file is missing from Google Drive, let’s assume that it throws a somelib.exceptions.wronglink error. Since the user doesn't know this, there could be issues if exceptions are thrown by the internal library. You can address this issue by wrapping the exception.

Here's an example of the implementation of this:

class  MyFileReaderLib(Exception):
   def  __init__(self, msg, somelib_exception):
      super(MyFileReaderLib, self).__init__(msg + (": %s" % somelib_exception)) 
      self.somelib_exception = somelib_exception

   def getFileOnline:
      try:
         return somelib.getfile('drive.google.com/u/2akjhaADS')
      except somelib.exceptions.wronglink as exep:
         raise MyFileReaderLib('Link is wrong or file not found', exep)

Python Exception Logging

Logging exceptions is just as important as catching exceptions. Logging simply means that imformation about exceptions should be logged for future reference - whether it be for debugging, postmortems, or anything else. You can use Python's logging library to accomplish this.

Here's a straightforward implementations of logging in Python:

import logging

try:  
   # some instruction that will raise an error

except  Exception  as e:
   logging.exception("message")

The instruction inside the except statement will produce a stack trace with the message. Note that the logging.exception method should only be used inside the except block. If it is used elsewhere, it might produce unexpected results (namely a tremendous amount of necessary log entries).

Else and Finally Statements

Adding an else statement to try-except statements broadens their functionality. More specifically, the else statement allows you to specify what should be done if no exceptions are raised.

Consider the following code as an example:

my_list = [0, 1, 2, 3]

try: 
   print(my_list[3])
except:
   print(100) 
else:
   print('No errors')

This generates:

3

No errors

By adding a finally statement to the try-except block, the program can perform a final task after executing the tasks inside the except block.

my_list = [0, 1, 2, 3]

try:
   print(my_list[5])
except:
   print(100)
else: 
   print('No errors')
finally:
   print('Just exiting try-block')

The above code generates:

100

Just exiting try-block.

Final Thoughts

Exceptions handling is used in programs to ensure that the flow of execution is not interrupted with any raised errors.

Exceptions are essential components of building robust Python applications. In the event any errors are raised, the program will follow the instructions defined in its except statements.

This tutorial serves as a broad introduction to exception handling in Python. While exception handling is a broad and complex topic, you should now have the information required to add your first exception management to your Python projects.

If you enjoyed this article, be sure to join my Developer Monthly newsletter, where I send out the latest news from the world of Python and JavaScript:


Written on June 25th, 2020