Python Exception Class Hierarchy

Python Exception Handling – ImportError and ModuleNotFoundError

Making our way through our detailed Python Exception Handling series we arrive at the ImportError, along with its single child subclass of ModuleNotFoundError. The ImportError is raised when an import statement has trouble successfully importing the specified module. Typically, such a problem is due to an invalid or incorrect path, which will raise a ModuleNotFoundError in Python 3.6 and newer versions.

Within this article we’ll explore the ImportError and ModuleNotFoundError in a bit more detail, beginning with where they sit in the overall Python Exception Class Hierarchy. We’ll also take a look at some simple code samples that illustrate the differences in import statement failures across newer (3.6) and older (2.7) versions of Python, so let’s get started!

The Technical Rundown

All Python exceptions inherit from the BaseException class, or extend from an inherited class therein. The full exception hierarchy of this error is:

Full Code Sample

Below is the full code sample we’ll be using in this article. It can be copied and pasted if you’d like to play with the code yourself and see how everything works.

When Should You Use It?

The seemingly simple import statement found in Python is actually rather complex when looking under the hood. At the most basic level an import statement is used to perform two tasks. First, it attempts to find the module specified by name, then loads and initializes it, if necessary. It also automatically defines a name in the local namespace within the scope of the associated import statement. This local name can then be used to reference the the accessed module throughout the following scoped code.

While the import statement is the most common technique used to gain access to code from other modules, Python also provides other methods and functions that makeup the built-in import system. Developers can opt to use specific functions to have more fine-grained control over the import process.

For our code samples we’ll stick to the common import statement that most of us are accustomed to. As mentioned in the introduction, behavior for failed imports differs depending on the Python version. To illustrate we start with the outer_import_2.7.py file:

The outer prefix for the file name indicates that we’re testing an “outer” or globally scoped import statement of gw_utility.Book. Executing this code produces the following output:

The overall issue here is that the gw_utility.Book module doesn’t exist. In fact, the proper module is lowercase: gw_utility.book. Since the import statement is at the top of the file, it exists outside our try-except block, so the ImportError we get in the log is not caught — execution was terminated entirely when the error was raised.

Alternatively, let’s see what happens if we move the import statement inside a try-except block, as seen in inner_import_2.7.py:

Running this code — also using Python 2.7 — produces the same ImportError, but we’re able to catch it and perform further processing of the caught ImportError, if necessary:

The ModuleNotFoundError was added in Python 3.6 as a subclass of ImportError and an explicit indication of the same kind of errors we’re seeing above in the 2.7 code. For example, let’s look at the outer import example in Python 3.6 with outer_import_3.6.py:

Once again, here we’re performing the import outside the try-except block, so running this code halts execution and produces the following output:

The cause of this error is the exact same as the 2.7 version, but with 3.6+ the more specific ModuleNotFoundError is now raised. Additionally, we can actually catch such errors if the import is executed within a try-except context:

This code allows us to output the Python version and process the error:

We’re also outputting the name and path attributes of the ImportError object, which were added in Python 3.3 to indicate the name of the module that was attempted to be imported, along with the path to the file that triggered the exception, if applicable. In this case our code is rather simple so, unfortunately, neither attribute is particularly useful.

Airbrake’s robust error monitoring software provides real-time error monitoring and automatic exception reporting for all your development projects. Airbrake’s state of the art web dashboard ensures you receive round-the-clock status updates on your application’s health and error rates. No matter what you’re working on, Airbrake easily integrates with all the most popular languages and frameworks. Plus, Airbrake makes it easy to customize exception parameters, while giving you complete control of the active error filter system, so you only gather the errors that matter most.

Check out Airbrake’s error monitoring software today and see for yourself why so many of the world’s best engineering teams use Airbrake to revolutionize their exception handling practices!