Explain Codes LogoExplain Codes Logo

Relative imports for the billionth time

python
relative-imports
absolute-imports
python-modules
Alex KataevbyAlex Kataev·Aug 23, 2024
TLDR

Relative imports are a facet of Python that can powerfully simplify your project's organization by relying on the hierarchy of directories within a package. By adding an __init__.py file, your directory blooms into a package. Use dot notation for your imports—. references the current directory, while .. indicates the parent directory. If you want to import module.py from a companion package:

from .sibling_package import module

Don't forget to launch your script as a module to preserve the hierarchical structure. Use -m for this:

python -m mypackage.myscript

Instead of calling scripts with relative imports directly (i.e., python myscript.py), maintain the script as a main module to ensure the package structure is upheld and relative paths are properly resolved.

Running scripts with the -m switch is akin to getting access to the entire blueprint of your package structure. When used, Python preserves the hierarchical context of your imports, allowing relative imports to function properly.

Scripts that form the entry point of your application should, ideally, reside outside your package directory structure. Utilize absolute imports to load the necessary modules and avoid the dreaded ImportError due to misplaced relative imports.

Python needs to find your package in sys.path to successfully import it. In certain scenarios (unit tests or deployment scripts), you may need to add your package directory to sys.path explicitly. But wield this power sparingly—dynamically changing sys.path can lead to cryptic bugs.

When working in interactive shells or REPL, remember that these are treated as __main__ and lack a package context. Avert the relative import path and use absolute imports.

When __main__ isn't so mean

It's important to note that scripts executed as __main__ can't use relative imports. If you want to use modules as scripts and still keep them importable, put your executable code under if __name__ == '__main__': like it's your secret code stash.

# Game of Thrones fan detected! winter_is_coming = __name__.split('.')[:-1] # Get current package

While this can be helpful, for a stable and readable codebase, prefer absolute imports when possible.

Ironing out misconceptions and common issues

There might be a few rough edges when you first start with relative imports:

  • Package Formalities: Do not forget to dress your directories with an __init__.py file in order to be accepted in the Python package party.
  • Avoid Manipulating sys.path: Delve into the world of sys.path only when you really need to. It’s not the magic “fix my imports” button.
  • Absolute Imports Are Absolute: When possible, try to use absolute imports. They present a clear portrayal of your package layout and are less prone to errors compared to their relative counterparts.
  • Running Modules: If you may recall, the -m switch is your ally to keep the package context intact.

At times, you might get a ModuleNotFoundError or ImportError when the Python interpreter can't place your module within a package. This is similar to forgetting your address when ordering hamburgers online—a classic facepalm moment.

Consider these tips to avoid such a predicament:

  • Always run modules using the -m switch.
  • Keep the __init__.py files in every directory of your package.
  • Make sure you are in the right place by checking the current working directory and sys.path.
  • When using scripts as entry points and referring to your package modules, stick with absolute imports.