The definitive guide to solve the infamous Python exception “ModuleNotFoundError”

Among common Python exceptions, the most infamous and time consuming one to solve is no doubt the “ModuleNotFoundError” but actually is pretty simple to fix once you understand a couple of concepts.
Fundamentally it can be raised for three reasons:

1. A typo or a wrong path specified in the import statement

This is the most easy to spot, and if you are using an IDE like PyCharm you will notice it immediately before running your code.

In order to reproduce the exception, let’s consider a project structure like:

/proj
    /foo
        __init__.py
        bar.py
    main.py

A main.py containing:

from fo.bar import BarClass
c = BarClass()

and bar.py containing:

class BarClass:
    pass

By using /proj as a current working directory and by running:

python main.py

We will obtain the following exception:

Traceback (most recent call last):
  File "/Users/dave/PycharmProjects/proj/main.py", line 1, in <module>
    from fo.bar import BarClass
ModuleNotFoundError: No module named 'fo'

To solve the problem, we have simply to change the import in order to match the right path (“foo.bar” instead of “fo.bar”):

from foo.bar import BarClass
c = BarClass()

So far, so easy… but let’s go on with scenario N.2

2. Execution context which requires an entry addition in sys.path that has not been satisfied

This one occurs when we are executing a python script with an import statement in a directory from which the interpreter cannot resolve the path to the required module defined in the import statement due to missing or bad configuration of the sys.path.
And, here you have first to understand how Python lookup for modules works, so I report the official documentation:

When a module named spam is imported, the interpreter first searches for a built-in module with that name. If not found, it then searches for a file named spam.py in a list of directories given by the variable sys.path. sys.path is initialized from these locations:

  1. The directory containing the input script (or the current directory when no file is specified).
  2. PYTHONPATH (a list of directory names, with the same syntax as the shell variable PATH).
  3. The installation-dependent default.

Let’s keep the structure of the scenario N.1, but with main.py containing:

class BaseClass:
    pass

and bar.py containing:

from main import BaseClass
c = BaseClass()

but now let’s change the working directory to “foo”, and launch the command:

python bar.py 

We will obtain the following exception:

Traceback (most recent call last):
  File "bar.py", line 1, in <module>
    from main import BaseClass
ModuleNotFoundError: No module named 'main'

Because since we are in the “foo” directory and we didn’t update the sys.path, Python is looking for a main.py file in that directory and obviously is not the case!
We can fix this issue in two ways: by using the PYTHONPATH environment variable or by extending the sys.path list.
To use the PYTHONPATH in a single shot, we can launch the script with the following command:

PYTHONPATH=../ python bar.py

In this way, we are practically saying “hey python, please consider also the parent directory for the module lookup”.
The same can be specified programmatically in this way:

import sys
sys.path.append('../')

Of course the code above must be written before the other import statement. Anyway my advice is to avoid such approach and to relay only on the PYTHONPATH environment variable.
Use sys.path instead to debug your current path resolution in this way:

import sys

for p in sys.path:
    print(p)

3. Circular dependency

This one is the most hateful that you can face. It happens when a module A requires something from a module B and in turn, the module B requires something from module A, thus generating a “deadly” circular reference.
In most cases it happens after an automatic refactoring with PyCharm (typically if you use the logging framework in the classical way)*, if it happens for other reasons it’s a signal that your software design is not sound and that you must review it carefully.

* for a classical usage of the logging framework I mean:

import logging

log = logging.getLogger(__name__)

class MyClass:
    def my_method(self):
        log.info('My method invoked')

then after moving MyClass to another module (via automatic refactoring), PyCharm tends to include an import of log (which 1. is not required since each module has its logger, 2. may cause the circular dependency).
To manually reproduce the exception, let’s consider a super simple structure like the following:

    /proj
        a.py
        b.py

With a.py containing:

from b import ClassB

class ClassA:
    def __init__(self):
        self.b = ClassB()

and b.py containing:

from a import ClassA

class ClassB:
    pass

a = ClassA()

By running python a.py in the project root, we will get the following exception:

Traceback (most recent call last):
  File "/Users/dave/PycharmProjects/proj/a.py", line 1, in <module>
    from b import ClassB
  File "/Users/dave/PycharmProjects/proj/b.py", line 1, in <module>
    from a import ClassA
  File "/Users/dave/PycharmProjects/proj/a.py", line 1, in <module>
    from b import ClassB
ImportError: cannot import name 'ClassB'

If we pay attention we can quite easily spot that this time we are facing a circular reference issue, since the stack trace is longer that the previous ones, and it prints a “ping-pong” between a.py and b.py.

  • Geza

    Unfortunately there are other situations as well. For example, what do you think the reason is when the project is configured to work with the Python 3.6 interpreter, but the latest PyCharm complains about the “math” library? It does work when you run the script.

  • ikukuvision

    2. Execution context which requires an entry addition in sys.path that has not been satisfied. This worked for me. Have made a massive note and pasted it. A life saver. Many many Thanks