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 viazipimport, or over network endpoints) and return aModuleSpec. - Loaders: Objects implementing
exec_module(module). They execute the moduleβs bytecode inside the newly allocatedmodule.__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:
- Refactor Shared Dependencies: Extract shared functions/classes into a third module (
module_c.py). - Deferred Imports: Move
import module_binside the specific function that uses it, running the import only when invoked. - 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.