8. The Python Import System#

Main takeaways:

  • Suppose we have a directory called root with the following files: package/module.py, and package/script.py.

  • From inside root, running python3 package/script.py will add package to sys.path meaning from package import module will raise an import error in script.py.

  • From inside root, running python3 -m package.script will add root to sys.path meaning from package import module will not raise an import error in script.py

When importing a package or module, python searches for it on its sys.path. Suppose we have the following project structure.

root
└── package
    ├── module.py
    └── script.py

Where module.py contains the following code.

def print_module():
    print("Print from module.py")

And script.py has the following code.

from package.directory import module

print_module()

Executing script.py from root gives us an import error.

$ python3 package/script.py
Traceback (most recent call last):
  File "/home/alex/root/package/script.py", line 1, in <module>
    from package.directory import module
ModuleNotFoundError: No module named 'package'

Even though we are running the code from the root directory which contains package, we cannot import module into script.py. Let us print sys.path at the top of script.py and rerun the script.

# script.py
import sys; print(sys.path)
from package import module

print_module()

Running script.py, we get.

$ python3 package/script.py
['/home/alex/root/package', '/usr/lib/python310.zip', '/usr/lib/python3.10', '/usr/lib/python3.10/lib-dynload', '/home/alex/documents/notes/.venv/lib/python3.10/site-packages']
Traceback (most recent call last):
  File "/home/alex/root/package/script.py", line 2, in <module>
    from package.directory import module
ModuleNotFoundError: No module named 'package'

The first element in the list points to our package directory. This means that python will look for package within the package directory. We want it to look for package for the root directory.

This import error can also be solved by removing package from the import path.

# script.py
import module

module.print_module()

The code now runs successfully from root.

$ python3 package/script.py
Print from module.py

However, if we move script.py into root we will get the same import error again. For clarity, moving script.py into root means our file structure now looks like this.

root
├── script.py
└── package
    └── module.py

Executing script.py results in the import error.

$ python3 script.py
Traceback (most recent call last):
  File "/home/alex/root/script.py", line 1, in <module>
    import module
ModuleNotFoundError: No module named 'module'

To get the script to run successfully, we would have to change our import from import module to from package import module.

For simplicity, I want to be able to import package.module into a script no matter where the script is in the root directory. We will discuss three ways of doing this but first let us change our project structure slightly to include script1.py and script2.py.

root
├── script1.py
└── package
    ├── script2.py
    └── module.py

Where script1.py and script2.py have just about the same code.

# script1.py
from package import module

module.print_module()
# script2.py
from package import module

module.print_module()

The first script, script1.py, runs successfully but script2.py does not.

$ python3 script1.py
Print from module.py
$ python3 package/script2.py
python3: can't open file '/home/alex/root/script2.py': [Errno 2] No such file or directory

Now we will run through three ways of solving this issue.

8.1. Running Scripts and Modules#

The -m flag can be used to run our program as a module instead of a script. This will make both of script1.py and script2.py work.

$ python3 -m script1
Print from module.py
$ python3 -m module.script2
Print from module.py

Let us run this experiment again but print sys.path at the start of the scripts.

# script1.py
import sys; print(sys.path)
from package import module

module.print_module()
# script2.py
import sys; print(sys.path)
from package import module

module.print_module()

Running the scripts gives us the following.

$ python3 -m package.script2
['/home/alex/root', '/usr/lib/python310.zip', '/usr/lib/python3.10', '/usr/lib/python3.10/lib-dynload', '/home/alex/documents/notes/.venv/lib/python3.10/site-packages']
Print from module.py
$ python3 -m script1
['/home/alex/root', '/usr/lib/python310.zip', '/usr/lib/python3.10', '/usr/lib/python3.10/lib-dynload', '/home/alex/documents/notes/.venv/lib/python3.10/site-packages']
Print from module.py

Running the scripts with -m flag adds the root directory to sys.path. If we cd into package and run script2.py we get the familiar import error.

$ cd package
$ python3 -m script2
['/home/alex/root/package', '/usr/lib/python310.zip', '/usr/lib/python3.10', '/usr/lib/python3.10/lib-dynload', '/home/alex/documents/notes/.venv/lib/python3.10/site-packages']
Traceback (most recent call last):
  File "/usr/lib/python3.10/runpy.py", line 196, in _run_module_as_main
    return _run_code(code, main_globals, None,
  File "/usr/lib/python3.10/runpy.py", line 86, in _run_code
    exec(code, run_globals)
  File "/home/alex/root/package/script2.py", line 2, in <module>
    from package import module
ModuleNotFoundError: No module named 'package'

So, as long as we run our scripts from root with the -m flag, we should be able to import package.module since root has been added to sys.path.