Exercises — Modules — import, from…import, as aliasing
Before we start, one picture of the whole idea: importing is just binding a name in your file that points at code living somewhere else.

L1 — Recognition
These check that you can read an import line and say what name it creates.
Exercise 1.1
For each line, state the single name (or names) that becomes usable in your file, and whether a dot prefix is required to reach the contents.
import math # (a)
from math import sqrt # (b)
import numpy as np # (c)
from datetime import datetime as dt # (d)Recall Solution 1.1
Ask for each: what label did this bind, and is the stuff behind a dot?
- (a)
import math→ binds the namemath. Contents are behind the dot:math.pi,math.sqrt. - (b)
from math import sqrt→ binds the namesqrtdirectly (no dot). You cannot writemath.sqrt— the namemathwas never bound here. - (c)
import numpy as np→ bindsnp(notnumpy). Contents behind the dot:np.array. The namenumpyis not bound. - (d)
from datetime import datetime as dt→ bindsdtdirectly (no dot).dt.now()works;datetime.now()does not (the namedatetimewas never bound).
Exercise 1.2
Which of these lines makes the name pi usable without any prefix?
import math # (a)
from math import pi # (b)
import math as m # (c)Recall Solution 1.2
Only (b). from math import pi copies the name pi straight into your file, so you write pi.
- (a) gives
math.pi(dot needed). - (c) gives
m.pi(dot needed, using the aliasm).
L2 — Application
Now you run the code in your head and produce output.
Exercise 2.1
What does this print?
import math
r = 3
area = math.pi * math.pow(r, 2)
print(round(area, 2))Recall Solution 2.1
math.pi.math.pow(3, 2)(a float).- Area
round(area, 2).
Output: 28.27. Note round needs no import — it's a built-in always present.
Exercise 2.2
What does this print?
from statistics import mean, median
data = [3, 1, 4, 1, 5, 9, 2]
print(round(mean(data), 3))
print(median(data))Recall Solution 2.2
- Sum , count , so
mean. median: sort , middle of 7 values is the 4th .
Output:
3.571
3
Exercise 2.3
What does this print, and why does the second import stay silent?
# mymod.py
print("loading mymod")
x = 10
# main.py
import mymod
import mymod
print(mymod.x + 5)Recall Solution 2.3
- First
import mymodruns the file → printsloading mymod, setsx = 10. - Second
import mymodfindsmymodalready insys.modules→ runs nothing. mymod.x + 5.
Output:
loading mymod
15
The top-level print fires exactly once — that's the caching guarantee. See sys and os modules for sys.modules.
L3 — Analysis
Here the code is legal but the naming interacts in tricky ways. Predict the result and explain the mechanism.
Exercise 3.1
Predict the output and name the bug's category.
from math import sqrt
def sqrt(n):
return "I am not real math!"
print(sqrt(16))Recall Solution 3.1
from math import sqrt copied the real sqrt into your namespace. Then def sqrt(...) rebinds the same name sqrt to your function. The last binding wins.
Output: I am not real math!
This is a silent shadowing bug: no error, wrong behaviour. With import math you'd call math.sqrt(16) and your sqrt couldn't collide. See Namespaces and Scope.
Exercise 3.2
You create a file random.py in your project folder containing just print("my file!"). In the same folder you run main.py:
# main.py
import random
print(random.randint(1, 6))What happens, and why?
Recall Solution 3.2
When Python resolves import random, it walks sys.path in order, and entry #1 is the script's own directory. Your random.py is found first, shadowing the standard-library random.
- So
import randomruns your file → printsmy file!. - Then
random.randint→AttributeError: your file has norandint.
Fix: rename your file (never shadow a stdlib name). This is the sys.path "first match wins" rule from the parent note.
Exercise 3.3
Order these sys.path locations by search priority (first checked → last), then say which foo wins:
- (P) the standard library folder,
- (Q) the directory of the running script,
- (R) directories listed in the
PYTHONPATHenvironment variable. A modulefooexists in both (Q) and (P).
Recall Solution 3.3
Search order: Q → R → P (script dir, then PYTHONPATH, then stdlib/installed).
Since foo lives in both Q and P, and Q is checked first, the script-directory foo wins. The stdlib copy is never reached — exactly the mechanism behind Exercise 3.2.
L4 — Synthesis
Combine forms, aliases, and the once-only rule to build something correct.
Exercise 4.1
Rewrite this messy block so that (i) no dot prefix clutters the date math, (ii) the class name doesn't collide with its module, and (iii) timedelta keeps its clear name. Then state what tomorrow.date() conceptually returns.
import datetime
tomorrow = datetime.datetime.now() + datetime.timedelta(days=1)Recall Solution 4.1
The pain is datetime.datetime — the module datetime contains a class also named datetime. Alias the class, import timedelta plainly:
from datetime import datetime as dt, timedelta
tomorrow = dt.now() + timedelta(days=1)
print(tomorrow.date())- (i) No
datetime.noise — names copied in directly. - (ii)
datetime as dtremoves the class/module name clash. - (iii)
timedeltais already unique and clear → left un-aliased.
tomorrow.date() returns the calendar date one day after now (a date object, time stripped off).
Exercise 4.2
A teammate wrote from math import * at the top of a 500-line file. Later, deep in the file, someone did gamma = 0.5. Now print(gamma) gives 0.5 but a call elsewhere gamma(5) raises TypeError: 'float' object is not callable. Explain the whole chain, then give the two safe rewrites.
Recall Solution 4.2
Chain of events:
from math import *dumped every public name ofmathinto the file — including the functionmath.gamma.gamma = 0.5rebound that same namegammato a float (silent shadowing — no warning).gamma(5)now tries to call a float →TypeError.
You can't even tell gamma came from math by reading the code — that's the readability cost of *.
Two safe rewrites:
from math import gamma # explicit — you see exactly what you took
# ...but still avoid reusing the name 'gamma' for your variable.or
import math # prefixed — math.gamma stays safe behind the dot
my_gamma = 0.5 # your own name can never clash with math.gammaThis is the DRY Principle served without the star's mess.
L5 — Mastery
Everything at once: caching, namespaces, search order, and reasoning about a subtle multi-import scenario.
Exercise 5.1
Predict the complete output, in order.
# greet.py
print("greet.py top-level runs")
message = "hi"
def shout():
return message.upper()
# main.py
import greet
from greet import shout, message
message = "bye"
print(greet.message)
print(message)
print(shout())Recall Solution 5.1
Step by step, tracking each name:
import greet→ runsgreet.pyonce → printsgreet.py top-level runs. Binds namegreet. Inside it,greet.message == "hi".from greet import shout, message→ does not re-run the file (already cached). Copies two names intomain:shout(the function) andmessage(currently"hi").message = "bye"→ rebinds main'smessageto"bye". This does not touchgreet.message— they are separate names in separate namespaces.print(greet.message)→ still"hi"(greet's own copy, untouched).print(message)→"bye"(main's copy).print(shout())→shoutreadsmessagefrom greet's namespace (where it was defined), which is still"hi"→ returns"HI".
Output:
greet.py top-level runs
hi
bye
HI
Key insight: from greet import message made a snapshot copy of the name — later reassigning message in main never propagates back, and shout() always sees its home namespace. This is the heart of Namespaces and Scope.
Exercise 5.2
Two files import each other's names. Given the caching rule, does this loop forever?
# a.py
print("a runs")
import b
A = 1
# b.py
print("b runs")
import a
B = 2
# main.py
import aState the print order (assume main.py is run).
Recall Solution 5.2
The sys.modules cache is what stops the loop. Trace it:
maindoesimport a→ Python registersainsys.modules(empty so far), starts runninga.py.a.pyprintsa runs, then hitsimport b→ registersb, starts runningb.py.b.pyprintsb runs, then hitsimport a→ais already insys.modules(half-built) → import returns immediately, no re-run.b.pyfinishes (B = 2), control returns toa.py.a.pyfinishes (A = 1).
Output:
a runs
b runs
Each file's top-level runs exactly once; the cache breaks the circular import. (Referencing a name that isn't defined yet during the half-built window can still fail — but here only the imports happen early, so it's safe.)
Recall One-line summary of every level
L1 recognise which name binds ::: import X→X (dot), from X import y→y (no dot), as Z→Z only.
L2 the once-only rule ::: repeated import re-runs nothing — cached in sys.modules.
L3 shadowing & search order ::: script dir wins on sys.path; a redefinition overwrites an imported name silently.
L4 clean composition ::: alias to kill clashes, list names explicitly, never import * in real code.
L5 namespaces are separate copies ::: from X import y snapshots the name; reassigning yours never edits X's.
Connections
- ← Back to the topic note
- Namespaces and Scope — why copied names and prefixed names never collide.
- sys and os modules —
sys.pathsearch order and thesys.modulescache. - Packages and __init__.py — how circular imports get restructured.
- DRY Principle — the reason all this reuse machinery exists.
- Python Functions — what modules mostly bundle up.