A module is a .py file; a package is a folder of modules. import finds, runs and caches them — understanding that cycle explains __name__, circular imports and sys.modules.
Importing
import math # whole module
from collections import Counter # one name
import datetime as dt # alias
print(math.sqrt(16), Counter("aab")["a"], dt.date(2026, 1, 1).year) # 4.0 2 2026Check the cache
If the name is already in
sys.modules, that module object is returned immediately.Find
Otherwise Python searches
sys.path: the script's folder,PYTHONPATH, the standard library, thensite-packages.Execute once
A new module object is created and the file's top-level code runs in its namespace.
Cache & bind
The module is stored in
sys.modulesand the name is bound in the importing namespace.
| Form | Binds | Use when |
|---|---|---|
import json | json | Default — the call site shows where names come from (json.dumps) |
import numpy as np | np | Conventional short aliases |
from pathlib import Path | Path | Names you use a lot |
from os import * | Every public name | Almost never — pollutes the namespace and hides origins |
Imports run once and are cached
import importlib, sys, tempfile
from pathlib import Path
folder = tempfile.mkdtemp()
Path(folder, "lvn_greet.py").write_text('print("module body runs")\nVALUE = 1\n')
sys.path.insert(0, folder)
import lvn_greet # runs the file: prints "module body runs"
import lvn_greet # cached in sys.modules: nothing printed
print("lvn_greet" in sys.modules, lvn_greet.VALUE)
lvn_greet.VALUE = 99
importlib.reload(lvn_greet) # re-executes the file (prints again), resetting VALUE
print(lvn_greet.VALUE)
sys.path.remove(folder)
del sys.modules["lvn_greet"]module body runs
True 1
module body runs
1if __name__ == "__main__":
# utils.py
def add(a, b):
return a + b
if __name__ == "__main__": # True only when run directly: python utils.py
print(add(2, 3)) # 5Module attributes
import json, os
print(json.__name__, os.path.__name__) # json posixpath
print(json.__file__.endswith("json/__init__.py")) # True ← json is a package
print(json.__doc__.splitlines()[0]) # JSON (JavaScript Object Notation) <https://json.org> is a subset of
print(__name__) # __main__| Attribute | Holds |
|---|---|
__name__ | The module's import name ("__main__" for the script being run) |
__file__ | Path of the source file (absent for built-in modules like sys) |
__doc__ | The module docstring — the first string literal in the file |
__all__ | Names exported by from module import * |
__path__ | Only on packages: where to look for submodules |
__all__ and import *
import sys, tempfile
from pathlib import Path
folder = tempfile.mkdtemp()
Path(folder, "lvn_shapes.py").write_text(
"__all__ = ['area']\n"
"def area(r): return 3 * r * r\n"
"def debug(): pass\n"
"def _helper(): pass\n"
)
sys.path.insert(0, folder)
from lvn_shapes import *
print("area" in dir(), "debug" in dir(), "_helper" in dir()) # True False False
sys.path.remove(folder)Without __all__, import * brings in every name that doesn't start with _. With it, only the listed names — __all__ is how a module declares its public API.
Packages
shop/
├── __init__.py # marks a regular package; runs on "import shop"
├── __main__.py # runs on "python -m shop"
├── cart.py
└── payments/
├── __init__.py
└── upi.py
from shop.cart import Cart # absolute import (preferred)
from .cart import Cart # relative import (only inside the package)
from ..payments import upi # two dots = parent packageImporting by name: importlib
import importlib
mod = importlib.import_module("json") # module name as a string
print(mod.dumps([1, 2])) # [1, 2]
plugin = "collections" # e.g. read from a config file
Counter = getattr(importlib.import_module(plugin), "Counter")
print(Counter("aab")["a"]) # 2Circular imports
a.py imports b, and b.py imports a. The second import finds a half-initialised in sys.modules, so names defined later in a don't exist yet.
ImportError: cannot import name 'User' from partially initialized module 'models'
(most likely due to a circular import)Restructure: move the shared code into a third module both can import (usually the real fix).
Import the module, not the name:
import modelsand usemodels.Userat call time.Move the import inside the function that needs it (deferred import).
For type hints only, guard with
if typing.TYPE_CHECKING:and use string annotations.
Virtual environments
Each project should install its dependencies into its own virtual environment — see Virtual Environments, pip & Packaging for the full workflow.
$ python3 -m venv .venv && source .venv/bin/activate
(.venv) $ pip install requestsWhat happens when you import a module twice?
How do you fix a circular import?