Understanding Python Relative Imports

October 26, 2025

One thing that has always tripped me up with Python is the import system. Everytime I think I understand how it works, I run some code and get more import errors. But I think I now have a complete understanding of how it works, so let's dive in.

What is sys.path?

To see how the Python import system works, we have to start by understanding where Python looks for code when there is an import. This is in the module search path, which is inititialized when Python starts. By printing the sys.path, we can see the list of paths that Python will be searching for modules. I created a folder named package, and we can see the repo structure below:

    package/
    |-- fruits/
    |   |-- __init__.py
    |   |-- apple/
    |       -- __init__.py
    |       -- slice.py
    |-- vegetables/
        |-- __init__.py
        |-- celery/
            -- __init__.py
            -- dice.py

Here is a sample output from print(sys.path):

[
    '/Users/Jeremy/Documents/Projects/package',
    '/Library/Frameworks/Python.framework/Versions/3.13/lib/python313.zip',
    '/Library/Frameworks/Python.framework/Versions/3.13/lib/python3.13',
    '/Library/Frameworks/Python.framework/Versions/3.13/lib/python3.13/lib-dynload',
    '/Library/Frameworks/Python.framework/Versions/3.13/lib/python3.13/site-packages'
]

There are five different paths that are listed. The first one is associated with the file I was running, and the remaining are different standard paths. Additionally, if you were to manually edit the PYTHONPATH, then those would be listed as well.

Let me also share what is in the two Python files.

# dice.py
import sys
from fruits.apple.slice import slice_apple

def dice_celery():
    return "Celery has been diced."

if __name__ == "__main__":
    print(sys.path)

# slice.py
import sys

def slice_apple():
    return "Apple has been sliced."

if __name__ == "__main__":
    print(sys.path)

Module vs. Script

From the root of the project in the package/ folder, the above sys.path output was from python3 -m vegetables.celery.dice. When I run that command with the -m option, the code is ran as a module. When you do that, the path that is added to sys.path[0] is from where the code is executed from. So if I run python3 -m apple.slice from the fruits/ directory, then sys.path[0] will be:

'/Users/Jeremy/Documents/Projects/package/fruits'

But what happens if I run python3 -m celery.dice from the vegetables/? We get an import error!

Traceback (most recent call last):
File "<frozen runpy>", line 198, in _run_module_as_main
File "<frozen runpy>", line 88, in _run_code
File "/Users/Jeremy/Documents/Projects/package/vegetables/celery/dice.py", line 2, in <module>
    from fruits.apple.slice import slice_apple
ModuleNotFoundError: No module named 'fruits'

Now why is that? We ran it as a module and it worked from the package/ directory and it worked. Well, if we comment out the import statement and see what the sys.path is, we get:

'/Users/Jeremy/Documents/Projects/package/vegetables'

Because our sys.path is in the vegetables/ directory, that is where we are telling Python our project root is. With that as the root location, Python will only look at folders that are downstream of the root path and will not infer anything upstream is part of the module.

What is important is that we are running the code as a module. If we run a progam just as a Python script, we have slightly different behavior. With the import still commented out in dice.py, regardless of where we run the code from, the value in sys.path[0] will be the location of the file itself.

Running python3 dice.py from the celery/ directory gives us:

'/Users/Jeremy/Documents/Projects/package/vegetables/celery'

If we ran python3 vegetables/celery/dice.py, we get the same outuput!

One thing to remember, your import has to be relative to where Python thinks your root directory is. This can be easy to forget as your project grows and you rework your folder structure.

Hopefully this will help you navigate the Python import system when working on your projects!