Python 101 – Exception Handling

Creating software is hard work. To make your software better, your application needs to keep working even when the unexpected happens. For example, let’s say your application needs to pull information down from the Internet. What happens if the person using your application loses their Internet connectivity?

Another common issue is what to do if the user enters invalid input. Or tries to open a file that your application doesn’t support.

All of these cases can be handled using Python’s built-in exception handling capabilities, which are commonly referred to as the try and except statements.

In this article you will learn about:

  • Common exceptions
  • Handling exceptions
  • Raising exceptions
  • Examining exception objects
  • Using the finally statement
  • Using the else statement

Let’s get starting by learning about some of the most common exceptions.

The Most Common Exceptions

Python supports lots of different exceptions. Here is a shortlist of the ones that you are likely to see when you first begin using the language:

  • Exception – The base exception that all the others are based on
  • AttributeError – Raised when an attribute reference or assignment fails.
  • ImportError – Raised when an import statement fails to find the module definition or when a from … import fails to find a name that is to be imported.
  • ModuleNotFoundError – A subclass of ImportError which is raised by import when a module could not be located
  • IndexError – Raised when a sequence subscript is out of range.
  • KeyError – Raised when a mapping (dictionary) key is not found in the set of existing keys.
  • KeyboardInterrupt – Raised when the user hits the interrupt key (normally Control-C or Delete).
  • NameError – Raised when a local or global name is not found.
  • OSError – Raised when a function returns a system-related error.
  • RuntimeError– Raised when an error is detected that doesn’t fall in any of the other categories.
  • SyntaxError – Raised when the parser encounters a syntax error.
  • TypeError – Raised when an operation or function is applied to an object of inappropriate type. The associated value is a string giving details about the type mismatch.
  • ValueError – Raised when a built-in operation or function receives an argument that has the right type but an inappropriate value, and the situation is not described by a more precise exception such as IndexError.
  • ZeroDivisionError – Raised when the second argument of a division or modulo operation is zero.

For a full listing of the built-in exceptions, you can check out the Python documentation here:

Now let’s find out how you can actually handle an exception when one occurs.

Handling Exceptions

Python comes with a special syntax that you can use to catch an exception. It is known as the try/except statement.

This is the basic form that you will use to catch an exception:

try:
    # Code that may raise an exception goes here
except ImportError:
    # Code that is executed when an exception occurs

You put code that you expect might have an issue inside the try block. This might be code that opens a file or code that gets input from the user. The second block is known as the except block. This code will only get executed if an ImportError is raised.

When you write the except without specifying the exception type, it is known as a bare exception. These are not recommended:

try:
    with open('example.txt') as file_handler:
        for line in file_handler:
            print(line)
except:
    print('An error occurred')

The reason it is bad practice to create a bare except is that you don’t know what types of exceptions you are catching. This can make figuring out what you did wrong more difficult. If you narrow the exception types down to the ones you expect, then the unexpected ones will actually make your application crash with a useful message.

At that point, you can decide if you want to catch that other condition or not.

Let’s say you want to catch multiple exceptions. Here is one way to do that:

try:
    with open('example.txt') as file_handler:
        for line in file_handler:
            print(line)
    import something
except OSError:
    print('An error occurred')
except ImportError:
    print('Unknown import!')

This exception handler will catch two types of exceptions: OSError and ImportError. If another type of exception occurs, this handler won’t catch it and your code will stop.

You can rewrite the code above to be a bit simpler by doing this:

try:
    with open('example.txt') as file_handler:
        for line in file_handler:
            print(line)
    import something
except (OSError, ImportError):
    print('An error occurred')

Of course, by creating a tuple of exceptions, this will obfuscate which exception has occurred. In other words, this code makes it harder to know which exception occurred.

Raising Exceptions

What do you do after you catch an exception? You have a couple of options. You can print out a message like you have been in the previous examples. You could also log the message to a log file. Or if the exception is one that you know needs to stop the execution of your application, you can re-raise the exception.

Raising an exception is the process of forcing an exception to occur. You raise exceptions in special cases. For example, if the application gets into a bad state, you might raise an exception. You will also tend to raise exceptions after already handling the exception.

You can use Python’s built-in raise statement to raise an exception:

try:
    raise ImportError
except ImportError:
    print('Caught an ImportError')

When you raise an exception, you can have it print out a custom message:

>>> raise Exception('Something bad happened!')
Traceback (most recent call last):
  Python Shell, prompt 1, line 1
builtins.Exception: Something bad happened!

If you don’t provide a message, then the exception would look like this:

>>> raise Exception
Traceback (most recent call last):
  Python Shell, prompt 2, line 1
builtins.Exception:

Now let’s learn about the exception object!

Examining the Exception Object

When an exception occurs, Python will create an exception object. You can examine the exception object by assigning it to a variable using the as statement:

>>> try:
...     raise ImportError('Bad import')
... except ImportError as error:
...     print(type(error))
...     print(error.args)
...     print(error)
... 
<class 'ImportError'>
('Bad import',)
Bad import

In this example, you assigned the ImportError object to error. Now you can use Python’s type() function to learn what kind of exception it was. This would allow you to solve the issue mentioned earlier in this article when you have a tuple of exceptions but you can’t immediately know which exception you caught.

If you want to dive even deeper into debugging exceptions, you should look up Python’s traceback module.

Using the finally Statement

There is more to the try/except statement than just try and except. You can add a finally statement to it as well. The finally statement is a block of code that will always get run even if there is an exception raised inside of the try statement.

You can use the finally statement for cleanup. For example, you might need to close a database connection or a file handle. To do that, you can wrap the code in a try/except/finally statement.

Let’s look at a contrived example:

>>> try:
...     1 / 0
... except ZeroDivisionError:
...     print('You can not divide by zero!')
... finally:
...     print('Cleaning up')
... 
You can not divide by zero!
Cleaning up

This example demonstrates how you can handle the ZeroDivisionError exception as well as add on clean up code.

But you can also skip the except statement entirely and create a try/finally instead:

>>> try:
...     1/0
... finally:
...     print('Cleaning up')
... 
Cleaning upTraceback (most recent call last):
  Python Shell, prompt 6, line 2
builtins.ZeroDivisionError: division by zero

This time you don’t handle the ZeroDivisionError exception, but the finally statement’s code block runs anyway.

Using the else Statement

There is one other statement that you can use with Python’s exception handling and that is the else statement. You can use the else statement to execute code when there are no exceptions.

Here is an example:

>>> try:
...     print('This is the try block')
... except IOError:
...     print('An IOError has occurred')
... else:
...     print('This is the else block')
... 
This is the try block
This is the else block

In this code, no exception occurred, so the try block and the else blocks both run.

Let’s try raising an IOError and see what happens:

>>> try:
...     raise IOError
...     print('This is the try block')
... except IOError:
...     print('An IOError has occurred')
... else:
...     print('This is the else block')
... 
An IOError has occurred

Since an exception was raised, only the try and the except blocks ran. Note that the try block stopped running at the raise statement. It never reached the print() function at all. Once an exception is raised, all the following code is skipped over and you go straight to the exception handling code.

Wrapping Up

Now you know the basics of using Python’s built-in exception handling. In this article you learned about the following topics:

  • Common exceptions
  • Handling exceptions
  • Raising exceptions
  • Examining exception objects
  • Using the finally statement
  • Using the else statement

Learning how to catch exceptions effectively takes practice. Once you have learned how to catch exceptions, you will be able to harden your code and make it work in a much nicer way even when the unexpected happens.

Related Reading