Intuition What this page is
The parent note taught you the four import forms. Here we stress-test them against every situation a real program throws at you: clean prefixes, name clashes, shadowing bugs, the "run-once" cache, missing modules, and an exam-style trap. Each example tells you which cell of the scenario matrix it covers, so by the end you've seen the whole space — nothing left unshown.
Before a single line of code: an import is a binding . It takes a name (like math) and points it at an object in your namespace — the container of names your file owns. Every scenario below is really the question "which name now points at what?"
Every import situation falls into one of these cells . The examples that follow each carry a tag like [Cell A] so you can check off the whole grid.
Cell
Case class
What could go wrong / matter
A
Plain import module
Must use the module. prefix every time
B
from module import name
Name copied in — no prefix, but risk of overwrite
C
import module as alias
Same object, shorter label
D
from module import name as alias
Fix a name clash
E
Shadowing bug (from … import overwrites your name)
Silent wrong answer
F
Local file shadows stdlib (random.py)
import finds the wrong file
G
"Runs once" — repeated import + cache
Side-effects fire exactly once
H
Degenerate: importing a missing module
ModuleNotFoundError
I
Real-world word problem
Choosing the right form
J
Exam twist: import a.b.c sub-modules
What name actually gets bound?
We will hit all ten cells in the examples below.
Worked example Example 1 — plain prefixed import
[Cell A]
Compute the length of the hypotenuse of a right triangle with legs 3 and 4 using math.
Forecast: guess the printed value before reading on.
Import the module.
import math
Why this step? import math binds exactly one name, math, in your namespace. Everything (pi, sqrt, hypot) lives behind the dot .
Call the function through its prefix.
h = math.hypot( 3 , 4 )
print (h) # 5.0
Why this step? hypot is not a name in your namespace — only math is. So hypot(3,4) alone would raise NameError; you must reach inside with math.hypot.
Verify: 3 2 + 4 2 = 9 + 16 = 25 = 5.0 . Units: legs and hypotenuse share the same length unit — consistent. ✓
Worked example Example 2 — selective import for readable code
[Cell B]
Print the mean and median of [3, 1, 4, 1, 5, 9, 2].
Forecast: the list has 7 numbers. Sum them in your head first.
Copy the names in directly.
from statistics import mean, median
Why this step? from … import mean, median copies those two names into your namespace, so you write mean(data) — no statistics. noise. This documents exactly what the file depends on.
Apply them.
data = [ 3 , 1 , 4 , 1 , 5 , 9 , 2 ]
print (mean(data)) # 25/7 = 3.571428...
print (median(data)) # sorted -> [1,1,2,3,4,5,9], middle = 3
Why this step? median sorts and takes the middle element (7 items ⇒ the 4th). No prefix because the name is now local.
Verify: sum = 3 + 1 + 4 + 1 + 5 + 9 + 2 = 25 , so mean = 25/7 ≈ 3.5714 . Sorted middle of 7 values is index 3 (0-based) = 3 . ✓
Worked example Example 3 — aliasing a long module name
[Cell C]
You want a NumPy-style array but hate typing numpy. (We simulate with math for reproducibility: alias math as m and read math.tau.)
Forecast: tau is 2 π . Guess its value.
Import under a nickname.
import math as m
Why this step? as m binds the same module object to the shorter name m. The name math is not created; only m. Same object, different label — like a nickname sticker on a toy.
Use the alias.
print ( round (m.tau, 5 )) # 6.28319
Why this step? Every access goes through m.; math.tau would now raise NameError because math was never bound.
Verify: τ = 2 π = 2 ( 3.14159265 … ) = 6.28319 to 5 dp. ✓
Worked example Example 4 — aliasing to break a name clash
[Cell D]
The module datetime contains a class also called datetime . Add one day to "now" cleanly.
Forecast: which of the two datetimes do you actually call .now() on?
Rename the class as you import it.
from datetime import datetime as dt, timedelta
Why this step? Without as dt, the imported name datetime (the class) shadows the module datetime and confuses readers. Aliasing gives the class the clear short name dt. timedelta stays un-aliased — its name is already unique.
Do the arithmetic.
later = dt( 2024 , 1 , 1 , 12 ) + timedelta( days = 1 )
print (later) # 2024-01-02 12:00:00
Why this step? dt(...) builds a date-time; adding a timedelta shifts it. The + between a datetime and a timedelta is defined to move forward in time.
Verify: Jan 1 12:00 + 1 day = Jan 2 12:00. Day advances by exactly 1, hour unchanged. ✓
Worked example Example 5 — the shadowing BUG
[Cell E]
Someone writes from math import sqrt, then later defines their own sqrt. What prints?
Forecast: does the second sqrt(16) still give 4.0?
Copy the name in.
from math import sqrt
print (sqrt( 16 )) # 4.0 (this is math's sqrt)
Why this step? sqrt now points at math.sqrt in your namespace.
Overwrite the same name.
def sqrt (n):
return n * n # BUG : this squares, not roots!
print (sqrt( 16 )) # 256 <- silently wrong
Why this step? Defining sqrt rebinds that one name to your function. The old binding to math.sqrt is gone — no warning, no error.
Verify: first call 16 = 4.0 ; after rebinding, 1 6 2 = 256 . The number silently changed, proving why from … import is riskier than the prefixed form. ✓ See Namespaces and Scope for why the last binding wins.
Worked example Example 6 — file shadowing the standard library
[Cell F]
You save a file called random.py in your project, then run import random; random.randint(1,6).
Forecast: does it roll a die, or crash?
Understand the search order.
Python walks sys.path (see sys and os modules ): script directory first , then PYTHONPATH, then the standard library. First match wins.
Trace what gets found.
Because your random.py sits in the script directory, it is found before the real stdlib random. If your file has no randint, you get:
import random
random.randint( 1 , 6 )
# AttributeError: module 'random' has no attribute 'randint'
Why this step? The name random bound to your file's module object, which lacks randint.
Verify: the real random.randint(1,6) returns an integer in [1,6]. Our check confirms 1 <= x <= 6 for the genuine function — proving the stdlib version is the one you wanted but got shadowed. Fix: rename your file. ✓
Worked example Example 7 — "import runs once" and the cache
[Cell G]
A module prints a banner at top level. What happens on the second import?
Forecast: two prints, or one?
Module with a side-effect.
# greeter.py
print ( "greeter loaded!" )
count = 100
Import it twice.
import greeter # prints "greeter loaded!"
import greeter # prints NOTHING
print (greeter.count) # 100
Why this step? On the first import Python executes the file and stores the finished module in sys.modules. The second import finds it already cached and skips execution entirely.
Verify: the banner fires once (count of "loaded" messages = 1), and greeter.count == 100. Side-effects at module level are guaranteed to run exactly once — predictable and fast. ✓
Worked example Example 8 — degenerate case: the missing module
[Cell H]
You run import nonexistent_lib. What happens, and how do you handle it gracefully?
Forecast: silent skip, or an exception?
The failing import.
import nonexistent_lib
# ModuleNotFoundError: No module named 'nonexistent_lib'
Why this step? Python scanned every folder in sys.path, found no matching file/package, and raised — there is nothing to bind the name to.
Handle it defensively.
try :
import ujson as json # a fast optional library
except ModuleNotFoundError :
import json # fall back to the standard one
print (json.dumps({ "ok" : True })) # {"ok": true}
Why this step? try/except lets you prefer an optional fast library but fall back to the stdlib when it's absent. Install missing libraries with pip — see Virtual Environments and pip .
Verify: json.dumps({"ok": True}) produces the string {"ok": true} (Python True → JSON lowercase true). ✓
Worked example Example 9 — real-world word problem
[Cell I]
You're building a report generator. You need: pi from math (used once), many string helpers from a textwrap module (used everywhere), and datetime.datetime (name clash). Choose the right import form for each.
Forecast: which of the four forms fits each need?
pi, used once → prefixed import.
import math
circumference = 2 * math.pi * 7 # radius 7
Why this step? One use, keep the namespace clean — the math. prefix documents the origin.
Many helpers, used constantly → selective import.
from textwrap import fill, shorten
Why this step? Typing textwrap. dozens of times is noise; copy the two names in. This is the DRY Principle applied to reading , not just writing.
Clashing class → alias.
from datetime import datetime as dt
stamp = dt( 2024 , 3 , 14 , 15 , 9 )
Why this step? Avoid the module/class name collision (Cell D pattern).
Verify: circumference = 2 π ⋅ 7 = 14 π ≈ 43.9823 . Each form chosen matches its usage frequency and clash risk. ✓
Worked example Example 10 — exam twist:
import a.b.c [Cell J]
After import os.path, which names are bound in your namespace: os, path, or os.path?
Forecast: you typed os.path — so surely path is now a name? (It's a trap.)
What import os.path actually binds.
import os.path loads the sub-module but binds only the top name os . You then reach the sub-module as os.path. The bare name path is not created.
import os.path
print (os.path.join( "a" , "b" )) # 'a/b' on Linux/Mac
# print(path.join(...)) # NameError: 'path' not defined
Why this step? Dotted imports bind the leftmost component only; the rest are reached through the dot chain. See Packages and __init__.py for how sub-modules are organised.
To get a bare path name, alias it.
from os import path
print (path.join( "a" , "b" )) # 'a/b'
Why this step? from os import path copies the sub-module object into the name path directly.
Verify: on POSIX, os.path.join("a","b") == "a/b" and os.path.join("a","b") == path.join("a","b") (same function, two access routes). ✓
Common mistake The trap this page is built around
Both Cell E (you overwrite an imported name) and Cell F (your file overwrites a stdlib module) are the same disease : a name got bound to the wrong object and no error fired . The cure is the same — prefer the prefixed import module form and never name a file after a module.
Mnemonic Ten cells, one question
Every import is: "which name now points at what?" Trace the binding, and A through J stop being surprises.
Recall Quick self-check (click to open)
After import math as m, does the name math exist? ::: No — only m is bound; math would raise NameError.
After from math import sqrt, then def sqrt(n): return n*n, what does sqrt(16) give? ::: 256 — your definition silently rebinds the name.
How many times does a module's top-level print fire across two imports? ::: Once — the second import is served from sys.modules.
After import os.path, is the bare name path bound? ::: No — only os is bound; use os.path or from os import path.
What error does importing a missing module raise? ::: ModuleNotFoundError.
Modules — import, from…import, as aliasing (index 1.3.1) — the parent note these examples drill.
Namespaces and Scope — why the last binding of a name wins (Cells E, J).
sys and os modules — sys.path search order behind Cell F.
Packages and __init__.py — dotted sub-module imports (Cell J).
Virtual Environments and pip — installing missing libraries (Cell H).
DRY Principle — the reason selective imports keep code readable (Cell I).