Visual walkthrough — Modules — import, from…import, as aliasing
We will trace one tiny made-up module the whole way:
# greet.py ← a file sitting on your disk
print("greet.py is running now") # a top-level line
message = "hello" # a top-level variable
def wave(): # a top-level function
return "o/"Keep this file in your head. Every figure refers to it.
Step 1 — The two "rooms" of names
WHAT. Before any import, picture two separate rooms. Room one is your file (main.py) — it holds the names you have typed. Room two does not exist yet: it is the room a module would give you. A namespace is exactly this — a room that holds names, each name being a label tied to a value.
WHY start here. Every single thing import does is "move a label from one room into another room." If you can see the rooms, you can predict every import form without memorising rules. This is the whole game.
PICTURE. Your room on the left starts nearly empty. The greet room on the right is greyed-out — it has not been built yet, because Python hasn't run the file.

Step 2 — FIND: Python hunts for the file
WHAT. You write import greet. Python's first job is to locate a file named greet.py. It walks a list of folders called ==sys.path== — top to bottom — and stops at the first folder that contains greet.py.
WHY a list, in order? Because the same name could exist in many folders. A fixed search order makes the answer deterministic: there is always exactly one winner, and it is always the first match.
PICTURE. The arrow scans folders from top. sys.path[0] is your script's own folder, so a greet.py sitting next to main.py wins immediately. The real standard library sits lower down.

Step 3 — EXECUTE: the file runs top to bottom (once)
WHAT. Having found greet.py, Python runs every top-level line of it, in order, inside a fresh namespace built for the module. Our file's print(...) fires (you see greet.py is running now), message = "hello" creates a name, and def wave creates a function name.
WHY run the whole file? Because a module is not magic text — it's ordinary code. The def and the = are just statements; the only way to create those names is to actually execute the statements that define them.
PICTURE. The greet room lights up. Each top-level statement drops one label into it: message, wave (the print produces output but leaves no name behind).

Step 4 — CACHE: record the finished room in sys.modules
WHAT. The moment execution finishes, Python files the completed module room into a dictionary called ==sys.modules==, under the key "greet". Think of it as a coat-check: the built module gets a ticket.
WHY cache? So the expensive "find + execute" work happens exactly once. Next time anyone imports greet, Python checks the coat-check first; finding the ticket, it skips straight past FIND and EXECUTE.
PICTURE. The built room is stored behind a coat-check window. A second import greet arrives, sees the ticket already hanging, and turns around — no re-running, no second print.

Step 5 — BIND: drop a label into your room
WHAT. Only now does the name you actually see appear. import greet places one label — greet — into your room, and that label points at the finished module room from Step 3.
WHY only one name? Because import greet is a promise to keep the module's contents behind the dot. Your room stays clean; to reach anything inside you write greet.message, greet.wave(). The dot is "walk into the greet room and read that label."
PICTURE. A single arrow labelled greet reaches from your room across to the module room. Everything inside (message, wave) is reachable only by following that arrow through the dot.

Step 6 — Case split: how each of the four forms moves labels
WHAT. Same machine (Steps 2–4 are identical for all forms), but the final BIND step differs. Here are all four side by side, so no form surprises you.
WHY compare on one picture? Because the only thing that varies between the forms is which labels land in which room. Seeing them together makes aliasing obvious: as just renames the label at the moment of binding.
PICTURE. Four mini-rooms show the arrows each form draws.

| Form | Label(s) placed in your room | Reach wave by |
|---|---|---|
import greet |
greet → module room |
greet.wave() |
from greet import wave |
wave (a copy) |
wave() |
import greet as g |
g → module room |
g.wave() |
from greet import wave as w |
w (a copy) |
w() |
Step 7 — Degenerate & edge cases (the ones that bite)
WHAT. Four boundary situations. Each is just the machine above behaving correctly — the surprise vanishes once you see the rooms.
WHY a whole step? Because these are exactly where beginners get "impossible" bugs. Covering them means you never hit an unshown scenario.
PICTURE. Each panel shows the machine producing the "surprising" result.

- Shadowing (
random.py). Your file issys.path[0]. FIND stops at your file; the real library is never reached. Fix: never name a file after a module you import. - Import twice, print once. Second
import greethits the cache (Step 4) → EXECUTE is skipped → no second print. This is a feature, not a bug. from greet import message, then you reassignmessage. The copied label is overwritten in your room; the module's ownmessageis untouched. Baremessagenow shows your value.- Missing file. No folder in
sys.pathcontainsgreet.py→ FIND fails →ModuleNotFoundError. The fix is usually a virtual environment / pip install or asys.pathproblem — not your code.
Recall Check yourself on the edge cases
After import greet twice, how many times does greet.py's top-level print fire? ::: Exactly once — the second import is served from sys.modules.
You keep a file math.py next to your script and write import math. Which file loads? ::: Yours — it sits in sys.path[0] and is found first, shadowing the standard library.
After from greet import message you write message = "bye". What is the module's own message? ::: Still "hello" — you only overwrote the copy in your own room.
The one-picture summary
The whole pipeline, compressed: one word import greet flows through FIND → EXECUTE → CACHE → BIND, and only the last box changes between the four forms.

Recall Feynman retelling — the whole walkthrough in plain words
You start with your own desk (your room) holding your own things. You say "get me greet." Python first hunts through a stack of drawers (sys.path) top to bottom until it finds a folder holding greet.py — the first one wins, which is why a same-named file of yours can steal the show. Finding it, Python reads the file out loud, once, running every top-level line, which builds a second desk full of that file's things (message, wave). It hangs that finished desk in a coat-check (sys.modules) so it never has to build it again — ask twice and the second time it just points at the coat-check, running nothing. Finally it puts one sticker on your desk. If you said import greet, the sticker is greet and it points across to the other desk (you reach things with a dot: greet.wave()). If you said from greet import wave, it instead copies the wave label onto your own desk so you can grab it bare — handy, but if you already had a wave, you just buried it. And as is nothing but choosing what the sticker says. Four forms, one machine, four different last stickers.
Connections
- Namespaces and Scope — the "rooms" are namespaces; BIND is name-binding.
- sys and os modules —
sys.path(FIND) andsys.modules(CACHE) are the two lists this page draws. - Packages and __init__.py — the same machine, but the "file" is a folder.
- Virtual Environments and pip — where the searched folders come from; fixes
ModuleNotFoundError. - DRY Principle — the reason we do all this: build once, borrow everywhere.
- Python Functions — the
waveinside our module is just a function bundled for reuse.