Imports, Modules, Packages & Import Resolution

When Python executes an import statement, it invokes CPython’s Import System (importlib). Rather than simply loading code from disk, CPython checks cache registries, queries Finder objects on sys.meta_path, loads module specs (ModuleSpec), and executes module code inside a new namespace dictionary.

This chapter details the sys.modules cache, sys.meta_path finder/loader protocol (PEP 451), circular import resolution, and package namespace architecture.


1. CPython Import Resolution Pipeline (PEP 451)

Executing import foo.bar triggers an explicit 4-step sequence inside importlib:

CPython Import Resolution Pipeline:

[ Execute: import foo.bar ]
            |
            v
[ 1. Check sys.modules Cache ]  <-- Is "foo.bar" already in sys.modules?
            |                         β”œβ”€β”€ YES: Return cached PyModuleObject instantly!
            |                         └── NO:  Continue search...
            v
[ 2. Iterate sys.meta_path Finders ]
     (PathFinder scans sys.path for .py / .pyc files)
            |
            v
[ 3. Create ModuleSpec Object ]
     (Stores module name, loader, origin path, and package flags)
            |
            v
[ 4. Loader Exec Module (exec_module) ]
     (Creates new module namespace dict & executes code in dict context)
            |
            v
[ Insert module into sys.modules & bind variable to caller scope ]

2. Finder and Loader Protocol (sys.meta_path)

The import mechanism is completely extensible via sys.meta_path:

  • Meta Path Finders: Objects implementing find_spec(fullname, path, target). They search for modules (e.g. on disk, inside zip files via zipimport, or over network endpoints) and return a ModuleSpec.
  • Loaders: Objects implementing exec_module(module). They execute the module’s bytecode inside the newly allocated module.__dict__.

3. Circular Import Deadlocks

A circular import occurs when Module A imports Module B, while Module B simultaneously imports Module A during top-level execution.

Circular Import Execution Deadlock:

[ Module A ]                             [ Module B ]
import module_b  ───────────>          import module_a
                                            |
                                            v
                                       Checks sys.modules for "module_a"
                                       (Finds INCOMPLETE Module A entry!)
                                            |
                                            v
                                       Attempts to access module_a.func()
                                       --> AttributeError: module 'a' has no attribute 'func'!

Production Fixes for Circular Imports:

  1. Refactor Shared Dependencies: Extract shared functions/classes into a third module (module_c.py).
  2. Deferred Imports: Move import module_b inside the specific function that uses it, running the import only when invoked.
  3. Type-Only Imports: Use if TYPE_CHECKING: for static type hints to prevent runtime import execution.

4. Namespace Packages (PEP 420)

Python 3.3+ supports Implicit Namespace Packagesβ€”packages without an __init__.py file. Namespace packages allow a single Python package (e.g. company.auth and company.billing) to be split across separate physical directories or independent git repositories on sys.path.

Display Options
Appearance
Text Size
100%