Coming from a static, strongly-typed language such as C# or Java, it was just a normal thing for me to reference other projects and custom types within those projects. No big deal.

But when I started using Python and wanted to import from parent directory, python would not allow it. While searching for a solution, I came to find out that in Python, importing a module from a parent or sibling directory is a different story altogether.

This post addresses this issue and proposes a solution.

The Problem

Suppose this is the directory structure of a project:

Our script to run is run.py, which will reference common/a.py.

a.py simply contains a single function:

def CommonFunction():
    print("This is where a gets called")

Now in run.py, we simply try to import a.py:

from common import a

print("Execution of run starts")
a.CommonFunction()
print("Execution of run finishes")

We run the script in Visual Studio Code by right clicking the script and choosing the option:

Or run from the terminal:

python3 run.py

You are going to get the following error:

ModuleNotFoundError: No module named 'common'

Explanation

Starting from Python 3.3, implicit relative references are allowed no more. That means that the ability to reference a module in the parent directory is not possible and becomes a major limitation.

So in order to reference a module, the directory that contains a module must be present on PYTHONPATH – PYTHONPATH is an environment variable which contains the list of packages that will be loaded by Python upon execution. What is in PYTHONPATH is also present in sys.path as well.

The directory from where a script or module is run is automatically added to PYTHONPATH. You can import any module that lies within this directory, but modules in its parent or sibling directories cannot be imported.

That is, unless you add the path to the desired module to PYTHONPATH or add it to sys.path.

Solution

Add the following code to run.py, I will explain the code in a bit:

import os, sys
currentdir = os.path.dirname(os.path.realpath(__file__))
parentdir = os.path.dirname(currentdir)
sys.path.append(parentdir)

from common import a

print("Execution of run starts")

a.CommonFunction()

print("Execution of run finishes")

Run the code again and you will see the expected output:

So what happened?

In the code, we import Python’s built-in modules os and sys.

Then we get the current directory using the current file, then get the parent directory using the current directory and just append it to the sys.path. What this does is that it adds the parent directory to sys.path. So now we can import any package & its modules into our script, thanks to appending to sys.path

You can go as many levels up as you wish by getting the parent directory recursively.

Alternative code

We can also slightly adjust the above code for programmatically updating sys.path as follows:

import sys, os, inspect
currentdir = os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe())))
parentdir = os.path.dirname(currentdir)
sys.path.insert(0, parentdir)

The change from the earlier code is that we use the inspect module to get the current file, and then use sys.path.insert.

Difference between sys.path.insert and sys.path.append

The difference between sys.path.insert and sys.path.append is that append will add the given path to the end of the sys.path‘s list, while insert is going to insert it at the given index(0 in our case).

Python searches for the directories to import by going through the list. Now if you have a package or module with the same name, then Python is going to load the module that comes first in the list and will ignore the one that comes later. So if you have name conflicts and defined files/folders with the same name, you must insert that path to sys.path at the first position.

Otherwise if you dont have naming conflicts, then the ordering of the imports does not matter and you can just append those paths to sys.path.

Conclusion

Although this solution is a bit hacky and it is recommended to not meddle with sys.path programmatically, it does get the job done when we have no other choice in Python but to import from parent directory using sys path. Using the os and sys module in Python gives us a way to go up a layer or two.