skip to content

The Import System

What happens when you type import: how a name becomes a module object, where Python searches, and what it caches. It explains the daily errors — a script that imports from one directory but not another.

part ofPythonoverview, primer and where to startread it →
on this pageshow

questions

page 1 of 2

In Python, what is the difference between an absolute import and a relative import?

level: juniorimportance: must knowfreq 62%

answer

  1. Two ways to name another module
  2. One counts from a root, one from here
  3. Leading dots, and what they count from
  4. Package name arithmetic, not directories
  5. PEP 328 deleted the implicit third form

basics

~20 s

An absolute import spells out the full dotted path from an import root, such as from app.core import config. An explicit relative import starts with dots that count outward from the importing module's own package: from . import config.

solid answer

~50 s

An absolute import names a module by its complete dotted path — `import app.core.config` or `from app.core import config` — and the machinery resolves that name against the import roots on `sys.path`, regardless of where the importing file lives. An explicit relative import starts with dots that count outward from the importing module's own package: `from . import config` means "the `config` module beside me", `from ..core import io` means "up one package, then into `core`". The dots are arithmetic on the module's `__package__`, not filesystem navigation and not the working directory. The relative form is legal only in a `from` statement — `import .config` is a `SyntaxError` — and only inside a module that was imported as part of a package. Python 3 removed implicit relative imports (PEP 328), so a bare `import config` inside a package is always absolute.

code

python · 5 lines
python
import json.decoder
from json import decoder

print(json.decoder.__name__)   # json.decoder
print(decoder.__package__)     # json

go deeper

for a junior

Be ready to read a leading dot correctly: one dot is the package the module is in, two dots is the package above it. Know that neither import form looks at the current working directory.

for a middle

Explain that dots are resolved against the importing module's __package__, that the relative form is legal only in a from-statement, and that Python 3 dropped the implicit relative lookup that Python 2 had.

for a senior

Show where each form pays off in real code: relative imports survive a rename or vendoring of the enclosing package, absolute imports are greppable, stable under moves within the tree, and keep working in a module executed on its own.

for a principal

Own the convention rather than the trivia: pick one style per repository, encode it in the lint configuration, and be able to justify it when packages are split across distributions, vendored, or re-namespaced.

### Two ways to spell a module name Every `import` statement hands the import machinery a **module name** to resolve. An *absolute* import gives that name in full: `import app.core.config`, or `from app.core import config`. The machinery walks the dotted name left to right, importing `app`, then `app.core`, then `app.core.config`, checking the module cache in `sys.modules` at each level and otherwise asking the finders that search the interpreter's import roots on `sys.path`. Nothing about where the *importing* file sits on disk takes part in that lookup — an absolute name means the same thing written in any module of any package. An *explicit relative* import gives a name that begins with one or more dots: `from . import config`, `from .config import TIMEOUT`, `from ..core import io`. The dots are not filesystem navigation and have nothing to do with the current working directory. They are arithmetic on the importing module's own **package name**: one dot means "the package I am in", two dots mean "one package up", three dots two up, and so on. The machinery reads that package name from the module's metadata — `__package__`, which for a normally imported module is the same value the module's `__spec__` reports as its parent — joins it with whatever follows the dots, and then resolves the resulting absolute name exactly as if you had typed it out. `importlib.util.resolve_name` performs that one step in isolation, which makes it the easiest way to see the rule: ```python from importlib.util import resolve_name resolve_name(".config", package="app.services") # 'app.services.config' resolve_name("..core.io", package="app.services") # 'app.core.io' ``` ### Why the relative form exists Inside a package, most references are to siblings. Writing `from . import config` instead of `from app.services import config` keeps the package's own top-level name out of every file, so the package can be renamed, nested under another project, or vendored into an application tree without editing its internals. The relative form also states an intent that the absolute form cannot: *this module, the one shipped beside me* — never a same-named module that happens to be installed in the environment. ### The syntax restriction The relative form is legal only in a `from ... import ...` statement. `import .config` is a `SyntaxError`. The reason is mechanical: the plain `import` statement binds the *leftmost* component of the dotted name as a name in the current namespace (`import a.b.c` binds `a`), and a leading dot gives it nothing to bind. So there are exactly two relative shapes: `from . import config`, which imports the sibling *module* and binds the module object, and `from .config import TIMEOUT`, which imports that same module and then pulls one attribute out of it. Both go through identical name resolution; they differ only in what ends up bound in your namespace. ### The third form that no longer exists Python 2 had *implicit* relative imports. Inside `app/services/loader.py`, a bare `import config` searched the module's own package first and only then fell back to a top-level `config`. That rule made it possible to shadow a standard-library name for one package by accident, and made a bare import statement ambiguous to read. PEP 328 removed it. In Python 3, up to and including 3.14, a dot-free name is **always** absolute: `import config` inside `app.services` will never find `app/services/config.py`, and will raise `ModuleNotFoundError` unless a top-level `config` exists. Every relative import in modern Python is therefore explicit, marked by its dots. ### Where a relative import cannot work Because resolution is arithmetic on the package name, a module with no package name has nothing to compute from. That is the case for a file the interpreter runs as the top-level script (on 3.14 its `__package__` and `__spec__` are both `None`), for code typed at the REPL, and for a module imported as a top-level module rather than as part of a package (its `__package__` is the empty string). All three raise `ImportError: attempted relative import with no known parent package`. A separate case is climbing too far: `from ...util import x` inside `app.services` counts up past the top-level package, which raises `ImportError: attempted relative import beyond top-level package`. Neither error consults the filesystem — the machinery never looks at a parent directory to see whether one "could" be a package. ### Reading them in review A useful summary: an absolute import is a coordinate; a relative import is a direction. The coordinate is unambiguous to a reader, greppable across a repository, and unaffected by where the file lives inside its package. The direction is shorter, survives a rename of the enclosing package, and pins the reference to the copy of the package the module ships in — but it changes meaning the moment the file moves to a different depth, and it fails outright in a module that was not imported as part of a package.

  • Why is `import .config` a syntax error when `from . import config` is fine?
    The plain `import` statement binds the leftmost component of the dotted name into the current namespace — `import a.b` binds `a`. A name that starts with a dot has no leftmost component to bind, so the grammar simply does not allow it. The relative form is therefore restricted to `from ... import ...`, where the names being bound are listed explicitly after `import`.
  • What is the difference between `from . import config` and `from .config import TIMEOUT`?
    Both resolve to the same module. The first imports the sibling module and binds the module object as `config`, so you write `config.TIMEOUT`. The second imports that module and then pulls one attribute out of it, binding `TIMEOUT` directly. The choice matters for readability and for circular-import behaviour, not for which module is loaded.
  • Inside `app/services/loader.py`, what does a bare `import config` find in Python 3?
    Only a top-level module or package named `config` on the import path — never the sibling `app/services/config.py`. Python 2's implicit relative lookup would have found the sibling first; PEP 328 removed that, so in Python 3 a dot-free name is always absolute and this raises `ModuleNotFoundError` if no top-level `config` exists.

An absolute import is a street address; a relative import is "two doors down on this street". Both find the same house, but only one still works after the street is renamed, and only one still makes sense once you have moved to another street.

saying these in an interview costs you the question

  • Says relative imports are resolved from the current working directory
  • Thinks a bare import inside a package finds the sibling file first
  • Believes import .config is valid syntax
  • Counts one leading dot as the parent package
  • Claims relative imports search sys.path for the dotted name
  • Says relative imports are deprecated in Python 3

context

open as a page

What does Python's ImportError 'cannot import name X from partially initialized module' mean?

level: juniorimportance: must knowfreq 58%

basics

~10 s

Two modules import each other. Python began running the first one, cached it half-finished, and the second one asked it for a name that has not been defined yet, so the name lookup fails.

open as a page

What does the if __name__ == "__main__": guard at the end of a Python file do?

level: juniorimportance: must knowfreq 78%

basics

~20 s

Python sets the module global name to "main" only in the file the interpreter was started with; an imported module gets its dotted import name instead. The guarded block therefore runs on direct execution and is skipped on import.

open as a page

What distinguishes a Python package from a plain module, and what does __init__.py do?

level: juniorimportance: must knowfreq 70%

basics

~20 s

A module is one importable unit of code, usually a single .py file. A package is a module that can also contain submodules: a directory whose init.py file is executed as the package's own body when it is first imported.

open as a page

Why does a Python directory with no __init__.py still import as a package?

level: juniorimportance: must knowfreq 45%

basics

~20 s

Since Python 3.3 (PEP 420) a directory with no init.py imports as an implicit namespace package: the search path held only matching directories, so Python builds a package that runs no code and whose file is None.

open as a page

Why does a file named random.py in your project break import random?

level: juniorimportance: must knowfreq 72%

basics

~10 s

Python searches sys.path in order, and the running script's own directory sits at the front, ahead of the standard library. Your random.py is found first, so import random binds your file instead.

open as a page

How do you turn a dotted config string like 'pkg.mod:ClassName' into the class object itself?

level: middleimportance: must knowfreq 55%

basics

~20 s

Split the string into a module part and an attribute part, call importlib.import_module on the module part, then getattr for the attribute. Prefer a colon separator so the split is unambiguous, validate the name against an allowlist, and raise a clear error naming the config key.

open as a page

In a circular import, why does `import mod` survive where `from mod import name` fails?

level: middleimportance: must knowfreq 48%

basics

~20 s

import mod binds the module object, which is already in the cache and gets filled in place, so a half-built module is fine. from mod import name copies an attribute value immediately, and during the cycle it may not exist yet.

open as a page

How does sys.meta_path drive what happens when Python runs an import statement?

level: middleimportance: must knowfreq 40%

basics

~20 s

Python asks each object in sys.meta_path, in order, to find the module by calling find_spec(name, path, target). The first finder that returns an importlib.machinery.ModuleSpec wins, and that spec names the loader which will actually execute the module.

open as a page

What does Python do with sys.modules when a module is imported a second time?

level: middleimportance: must knowfreq 58%

basics

~20 s

It finds the module already there and stops. The import statement checks sys.modules by name first, and on a hit it only binds the name in the importing namespace; the module body runs exactly once per interpreter.

open as a page

What does importlib.import_module do that a plain import statement cannot?

level: juniorimportance: should knowfreq 42%

basics

~20 s

importlib.import_module takes the module name as a string computed at runtime and returns that exact module object. The import statement needs the name spelled out in source code, and the low-level import builtin returns the top-level package instead of the submodule you asked for.

open as a page

Why does `from . import config` fail with 'attempted relative import with no known parent package'?

level: middleimportance: should knowfreq 55%

basics

~20 s

Leading dots resolve against the importing module's package name in package. A module that Python did not load as part of a package has no package name, so there is nothing to count outward from and the import machinery raises ImportError.

open as a page

How does importlib.metadata.entry_points discover installed plugins without importing them?

level: middleimportance: should knowfreq 26%

basics

~20 s

It reads the metadata files that installed distributions leave on sys.path, so discovery is a file scan rather than an import. Each result carries a name and a module:attribute target string; calling EntryPoint.load() is the step that actually imports and resolves it.

open as a page

What does an importlib.machinery.ModuleSpec carry, and how does a loader use it?

level: middleimportance: should knowfreq 22%

basics

~20 s

A ModuleSpec carries the module's name, the loader that can execute it, its origin, and for a package its submodule_search_locations. The machinery builds a module object from the spec, registers it under its name, then calls loader.exec_module(module) to run the body.

open as a page

How does python -m catalogue.loader differ from python catalogue/loader.py in what it puts on sys.path[0]?

level: middleimportance: should knowfreq 46%

basics

~20 s

The -m form puts the current working directory at the front of sys.path, so the package being run stays importable. The script-path form puts the script's own directory there instead, so the parent package is not on the path at all.

open as a page

What role does __main__.py play when Python runs a package, a directory or a .zip file?

level: middleimportance: should knowfreq 30%

basics

~20 s

main.py is what the interpreter executes when the target is not a single .py file. With -m the package is imported first and its main.py runs as the main module; a directory or zip path is prepended to sys.path and its top-level main.py is executed.

open as a page

How would you use a module-level `__getattr__` to keep a moved name importable?

level: middleimportance: should knowfreq 24%

basics

~10 s

Map each old name to its new home, then in the module's getattr warn with DeprecationWarning and stacklevel=2 and return the object from its new location. Raise AttributeError for anything else.

open as a page

When does Python call a module-level `__getattr__` defined by PEP 562?

level: middleimportance: should knowfreq 30%

basics

~20 s

Only as a fallback. Attribute lookup on a module searches the module's globals first; when the name is missing there, Python calls the module's getattr with it. It must raise AttributeError for names it cannot supply.

open as a page

Why does `import xml` leave `xml.etree` an AttributeError while `import xml.etree` works?

level: middleimportance: should knowfreq 45%

basics

~20 s

Importing a package does not import its submodules. Plain import xml runs only xml/init.py; the name etree becomes an attribute of the xml module object only once xml.etree is itself imported, by you or by someone else.

open as a page

How does Python assemble a namespace package's __path__ from several sys.path entries?

level: middleimportance: should knowfreq 35%

basics

~20 s

Python walks every sys.path entry in order, records each same-named directory that has no init.py as a portion, and puts them all in path. A module file or a directory with init.py stops the walk instead.

open as a page

How does CPython assemble sys.path when the interpreter starts?

level: middleimportance: should knowfreq 48%

basics

~10 s

In order: an entry for the script's directory or the current directory, then PYTHONPATH entries, then the interpreter's own standard-library directories, then the site-packages directories that the site module appends while processing .pth files.

open as a page

Inside your own importable package, when do relative imports beat absolute ones?

level: seniorimportance: should knowfreq 38%

basics

~20 s

Use relative imports for references inside a package that may be renamed, nested or vendored: they follow the package wherever it goes and always pin the sibling that ships with it. Use absolute imports across packages and in entry-point modules.

open as a page

Why can importlib.reload leave parts of a running program still executing the old code?

level: seniorimportance: should knowfreq 32%

basics

~20 s

importlib.reload re-executes the source into the same module object, so only attribute lookups through that module see new code. Names copied out by from-imports, existing instances and registered callbacks still reference the old function and class objects, which stay alive.

open as a page

A circular import between `routing.planner` and `routing.solver` fails only when the solver is imported first — why is a cycle order-dependent, and how do you find its entry point?

level: seniorimportance: should knowfreq 33%

basics

~20 s

Whichever module is imported first is the one left half-built, so the cycle only breaks when the name the other module needs sits below the line that starts it. Reproduce each entry order in a fresh process.

open as a page

How does `typing.TYPE_CHECKING` break an import cycle caused only by type annotations?

level: seniorimportance: should knowfreq 42%

basics

~10 s

typing.TYPE_CHECKING is False at run time and True for type checkers, so an import inside if TYPE_CHECKING: never executes and cannot create a cycle, while the checker still sees the name for annotations.

open as a page

How does importlib.util.LazyLoader defer a module's execution, and when does that backfire?

level: seniorimportance: should knowfreq 20%

basics

~20 s

LazyLoader wraps a real loader. The module object it produces is a placeholder whose class is swapped, so the wrapped loader runs the body on the first attribute access instead of at import. Import cost moves to first use, and so do import-time errors and side effects.

open as a page

Why can python -m catalogue.loader leave two copies of loader.py's state in one process?

level: seniorimportance: should knowfreq 28%

basics

~20 s

The file executes once as main and again under its dotted name if anything imports it, because sys.modules is keyed by module name rather than by file path. Each execution builds separate globals: two caches, two class objects, two sets of side effects.

open as a page

A package's __init__.py imports all its submodules eagerly — what does that cost you?

level: seniorimportance: should knowfreq 35%

basics

~20 s

Everything init.py imports is paid for by every process that touches any part of the package, before any of its own code runs. That means slower start-up, more memory, import-time side effects, and hidden coupling between submodules.

open as a page

Why can a package missing its __init__.py import from the source tree but vanish from the installed wheel?

level: seniorimportance: should knowfreq 30%

basics

~20 s

Running from the checkout puts the source directory on sys.path, so the bare directory imports as a PEP 420 namespace package. Build backends discover packages by looking for init.py, so it never enters the wheel.

open as a page

Why can sys.modules hold the same source file twice under two different names?

level: seniorimportance: should knowfreq 32%

basics

~20 s

Because the cache is keyed by module name, not by file. A file reachable under two names — typically when a package and its parent are both on sys.path — misses the cache twice and becomes two modules.

open as a page

showing 1–30 of 34