Question presented to candidate:
"Walk me through what actually happens when Python runs import foo.bar. Where does it look, what objects are involved, why does a module's code run only once, and why do circular imports sometimes fail with 'partially initialized module'?"
What a strong answer should cover:
importis a run-time statement (IMPORT_NAMEin bytecode), not a textual include; the first stop is thesys.modulescache, which is why a module body executes only once per process- On a cache miss, each finder on
sys.meta_pathis askedfind_spec(name, path); the defaults areBuiltinImporter,FrozenImporterandPathFinder, which walkssys.path(or the parent package__path__) using path-entry finders fromsys.path_hooks, cached insys.path_importer_cache - The finder returns a
ModuleSpec(name, loader, origin,submodule_search_locations); the loader creates the module, it is put insys.modulesbefore the body runs, thenexec_moduleruns the code; on error the entry is removed - Dotted imports import each parent first (
foo, thenfoo.barusingfoo.__path__);import foo.barbindsfoo,from foo import barbindsbar; relative imports are resolved against__package__ - Circular imports work only when the half-initialised module already has the names being asked for; fixes are importing the module instead of names, moving the import, or restructuring
- Source files are compiled to
__pycache__/*.cpython-3XX.pyc, revalidated by source mtime and size by default (hash-based pycs since 3.7, PEP 552);importlib.reloadre-executes into the same object
Clarifying questions expected:
- Are we talking about CPython's default machinery, or a custom setup (zip apps, frozen apps, a custom finder)?
- Is the interest in how to debug a specific import error (wrong module picked up, circular import)?
Code / implementation expected: Optional โ a custom MetaPathFinder / Loader, or a circular-import repro and its fix.