When I first started working with Python, nothing stumped me more than how bizarre Python’s import system seemed to be. Often time, I wanted to run a module inside of a package with the python src/sub/module.py command, and it’d throw an ImportError that didn’t make any sense. Consider this package structure:

├── __init__.py
├── a.py
└── sub
    ├── __init__.py
    └── b.py

Let’s say you’re importing module a in module b:

# b.py
from src import a


Now, if you try to run module b.py with the following command, it’d throw an import error:

python src/sub/b.py
Traceback (most recent call last):
  File "/home/rednafi/canvas/personal/reflections/src/sub/b.py", line 2, in <module>
    from src import a
ModuleNotFoundError: No module named 'src'

What! But you can see the src/a.py module right there. Why can’t Python access the module here? Turns out Python puts the path of the module that you’re trying to access to the top of the sys.path stack. Let’s print the sys.path before importing module a in the src/sub/b.py file:

# b.py
import sys

from src import a

Now running this module with python src/sub/b.py will print the following:

['/home/rednafi/canvas/personal/reflections/src/sub', '/usr/lib/python310.zip', '/usr/
lib/python3.10', '/usr/lib/python3.10/lib-dynload', '/home/rednafi/canvas/personal/

Traceback (most recent call last):
  File "/home/rednafi/canvas/personal/reflections/src/sub/b.py", line 5, in <module>
    from src import a
ModuleNotFoundError: No module named 'src'

From the first section of the above output, it’s evident that Python looks for the imported module in the src/sub/ directory, not in the root directory from where the command is being executed. That’s why it can’t find the a.py module because it exists in a directory above the sys.path’s first entry. To solve this, you should run the module with the -m switch as follows:

python -m src.sub.b

This will not raise the import error and return the following output:

['/home/rednafi/canvas/personal/reflections', '/usr/lib/python310.zip',
'/usr/lib/python3.10', '/usr/lib/python3.10/lib-dynload',

Here, the first entry denotes the root directory from where the script is being run from. Voila, problem solved!

Recent posts

  • Protobuffed contracts
  • TypeIs does what I thought TypeGuard would do in Python
  • ETag and HTTP caching
  • Crossing the CORS crossroad
  • Dysfunctional options pattern in Go
  • Einstellung effect
  • Strategy pattern in Go
  • Anemic stack traces in Go
  • Retry function in Go
  • Type assertion vs type switches in Go