A workshop of labelled drawers#
Picture a workshop wall covered in drawers, each with a label: MATH, RANDOM, TIME, TURTLE. Inside each drawer are tools. The tools exist the whole time, but you cannot use one until you open its drawer, and when you use it you say which drawer it came from, so nobody confuses the hammer from WOODWORK with the one from UPHOLSTERY.
A module is one of those drawers: a named collection of functions and values that you can load into your program. Usually it is a file of Python code; a few, such as math, are built into Python itself. import opens the drawer. The dot, as in math.sqrt, says which drawer the tool came from. A few tools such as print, len and round are always on the bench with no import needed; they are called built-in functions.
import and dot notation#
Write the import line once, at the top of the file. After that, use the module’s name, a dot, then the tool’s name:
import math
print(math.sqrt(16))
print(math.pi)
print(math.sqrt(2) * math.sqrt(2))
4.0
3.141592653589793
2.0000000000000004
math.sqrt(16) is dot notation: “the sqrt that lives in math”. Notice that math.sqrt returns 4.0, a float, even for a perfect square, and math.pi has no brackets because it is a value, not a function. The third line is a reminder that floats are stored in binary and are almost never exact: the square root of 2 is stored very slightly wrong, so squaring it gives 2.0000000000000004 rather than 2.0. Compare floats with a tolerance (or math.isclose), never with ==.
Forget the import and Python has never heard of math:
print(math.sqrt(16))
Traceback (most recent call last):
File "main.py", line 1, in <module>
print(math.sqrt(16))
^^^^
NameError: name 'math' is not defined
There are two other ways to import, and each has a trade-off:
| Form | Then you write | Trade-off |
|---|---|---|
import math | math.sqrt(2) | longest to type, clearest about where each name comes from |
from math import sqrt, pi | sqrt(2) | shorter, but only the names you listed become available in your file (the whole module is still loaded), and they can clash with your own names |
import math as m | m.sqrt(2) | an alias: a shorter nickname for the module |
You may also see from math import *, which dumps every name from the module into your program. Avoid it: you can no longer tell where a name came from, and one of them may quietly replace a variable of your own.
The math module#
import math
print(math.floor(3.7), math.ceil(3.2))
print(math.floor(-3.2), math.ceil(-3.7))
print(int(3.7), int(-3.7))
print(math.pow(2, 3), 2 ** 3)
print(math.fabs(-5), abs(-5))
print(round(2.5), round(3.5), round(2.675, 2))
3 4
-4 -3
3 -3
8.0 8
5.0 5
2 4 2.67
Three details from that output are worth knowing for an exam:
flooralways rounds down andceilalways rounds up, and “down” for a negative number means further from zero:floor(-3.2)is-4. Both return anint.int(-3.7)just chops off the decimals, giving-3. That is truncation, not rounding, and it differs fromfloorfor negatives.math.powalways returns a float, while the**operator keeps whole numbers whole as long as the exponent is not negative (2 ** -1is0.5).
The built-in round (no import) has a surprise of its own: when a number is exactly half way, it rounds to the nearest even number. So round(2.5) is 2 and round(3.5) is 4. This is called banker’s rounding, and it is deliberate. The last value, round(2.675, 2) giving 2.67, has a different cause: 2.675 cannot be stored exactly in binary, and the stored value is a hair below it, so it rounds down.
The random module#
| Call | Gives you |
|---|---|
random.random() | a float from 0.0 up to, but not including, 1.0 |
random.randint(a, b) | a whole number from a to b, including both ends |
random.randrange(a, b) | a whole number from a up to b, not including b, like range |
random.choice(seq) | one item picked from a list or string |
random.shuffle(lst) | mixes up a list in place and returns None |
random.seed(n) | starts the random sequence at a fixed point, so the same numbers come out every run |
The numbers are not truly random. They come from a formula that produces a sequence that looks random. Setting a seed picks where in that sequence to start, which is handy for testing: the program then behaves the same every time. That is how this listing can show its output at all:
import random
random.seed(42)
print(random.randint(1, 6), random.randint(1, 6), random.randint(1, 6))
print(round(random.random(), 4))
print(random.choice(["rock", "paper", "scissors"]))
deck = [1, 2, 3, 4, 5]
random.shuffle(deck)
print(deck)
6 1 1
0.7416
rock
[4, 1, 3, 5, 2]
Run it twice and you get exactly those numbers again. Remove the seed line and they change every run. Notice that shuffle changed deck itself; writing deck = random.shuffle(deck) would replace your list with None, the same trap as sort.
A common use is a simulation. Roll a die 6,000 times and count the results; each face should come up roughly 1,000 times:
import random
random.seed(7)
counts = [0, 0, 0, 0, 0, 0]
for roll in range(6000):
face = random.randint(1, 6)
counts[face - 1] = counts[face - 1] + 1
print(counts)
print(sum(counts))
[1038, 1020, 1000, 969, 953, 1020]
6000
Writing your own module#
Any .py file is a module. Put some functions in a file, save it in the same folder as your program, and import it by its file name without the .py:
The file geometry.py holds:
"""Small geometry helpers."""
PI = 3.14159
def circle_area(r):
return PI * r * r
def rectangle_area(w, h):
return w * h
import geometry
print(geometry.circle_area(2))
print(geometry.rectangle_area(3, 4))
print(geometry.PI)
12.56636
12
3.14159
The module’s functions and its constant PI are all reached with dot notation, exactly like math. Splitting a program into modules keeps each file short, lets several programs share the same functions, and is how every large Python program is organised.
if __name__ == "__main__"#
When Python imports a module, it runs the whole file once. So any test code you left at the bottom of the module runs too, inside the program that imported it:
The file tools.py holds:
def double(n):
return n * 2
print("testing double:", double(21))
import tools
print("main program says", tools.double(5))
testing double: 42
main program says 10
The fix is a guard. Python sets a hidden variable called __name__ in every file: it is "__main__" in the file you ran directly, and the module’s own name in a file that was imported. So code under this if runs only when you run the module itself:
The file tools.py holds:
def double(n):
return n * 2
if __name__ == "__main__":
print("testing double:", double(21))
import tools
print("main program says", tools.double(5))
main program says 10
Run tools.py directly and the test lines run. Import it and they stay quiet. Many programs put their whole main code in a main() function and call it under this guard.
Three import errors and what they mean#
A misspelt module name fails at the import line:
import maths
Traceback (most recent call last):
File "main.py", line 1, in <module>
import maths
ModuleNotFoundError: No module named 'maths'
A misspelt tool name imports fine and fails when used. The module exists; the thing inside it does not:
import math
print(math.squareroot(16))
Traceback (most recent call last):
File "main.py", line 3, in <module>
print(math.squareroot(16))
^^^^^^^^^^^^^^^
AttributeError: module 'math' has no attribute 'squareroot'
A file of your own with the same name as a library module is the sneaky one. Python looks in your program’s folder before the standard library, so a file called random.py sitting there is imported instead of the real random:
The file random.py holds:
print("This is my own random.py")
import random
print(random.randint(1, 6))
This is my own random.py
Traceback (most recent call last):
File "main.py", line 3, in <module>
print(random.randint(1, 6))
^^^^^^^^^^^^^^
AttributeError: module 'random' has no attribute 'randint'
The message says random has no randint, which sounds impossible, until you see the first line of output: the random that loaded was your own file. Never name your files after modules you use: random.py, turtle.py, csv.py. (A few modules, such as math, are built into Python and win anyway, but the habit is worth keeping for all of them.)
import loads a module once at the top; dot notation (math.sqrt) says which module a tool comes from. randint includes both ends, seed makes randomness repeatable, and code under if __name__ == "__main__": runs only when the file is run directly.
Where these go wrong#
- Using a module without importing it.
NameError: name 'math' is not defined. Addimport mathat the top. - Writing
sqrt(16)afterimport math. With a plain import you must writemath.sqrt. Onlyfrom math import sqrtlets you drop the prefix. - Treating
randint(1, 6)likerange(1, 6).randintincludes 6;rangeandrandrangestop before it. deck = random.shuffle(deck).shufflechanges the list in place and returnsNone.- Expecting
floorandintto agree on negatives.math.floor(-3.7)is -4;int(-3.7)is -3. - Expecting
round(2.5)to be 3. Halves round to the nearest even number, so it is 2. - Naming your file after a module, such as
random.pyorturtle.py. Your file is imported instead of the real one. - Test code at the bottom of a module with no
__name__guard. It runs every time the module is imported.
Practice#
Answer each one on paper before you look at the solutions. Every answer that involves running code was checked by running it.
- What does each line print?
import math, thenprint(math.sqrt(25)),print(math.floor(-2.5)),print(math.ceil(4.01)),print(int(-2.5)). - Which of these can produce the value 10? (a)
random.randint(1, 10)(b)random.randrange(1, 10)(c)random.choice(range(1, 11)) - Write a function
hypotenuse(a, b)usingfrom math import sqrt, and test it with 3 and 4. - Simulate 1,000 coin flips with
random.randint(0, 1)and count heads. Use a seed so the result is repeatable. - A student’s dice program,
dice.py, sits in a folder that also holds an old file they namedrandom.py.dice.pyrunsimport randomthenrandom.randint(1, 6)and crashes withAttributeError: module 'random' has no attribute 'randint'. Explain and fix. - A module
shapes.pyends withprint(area(2, 3))as a quick test. What happens when another program runsimport shapes, and how do you stop it?
Solutions#
5.0(sqrt returns a float),-3(floor goes down, further from zero),5(ceil goes up),-2(int truncates toward zero).- (a) and (c).
randintincludes both ends.randrange(1, 10)stops before 10, likerange.range(1, 11)includes 10, sochoicecan pick it. - With
from math import sqrt, the function is used without themath.prefix:from math import sqrt def hypotenuse(a, b): return sqrt(a ** 2 + b ** 2) print(hypotenuse(3, 4)) print(round(hypotenuse(1, 1), 3))5.0 1.414 - Count every flip that comes up 1. With seed 1 the result is the same every run; without it, it changes each time but stays close to 500:
import random random.seed(1) heads = 0 for flip in range(1000): if random.randint(0, 1) == 1: heads = heads + 1 print("heads:", heads, "tails:", 1000 - heads)heads: 515 tails: 485 - Python finds the student’s own
random.pyin the program’s folder before the standard library, soimport randomimports that file, which has norandint. Rename or deleterandom.py. (If the program file itself is namedrandom.py, the message sayspartially initialized module 'random'instead; the cause and the fix are the same.) - Importing runs the whole file, so the test line prints inside the other program. Put it under
if __name__ == "__main__":, which is true only whenshapes.pyitself is the file being run.
Test-readiness checklist#
These are the skills a first programming course tests on this topic. Tick each one only when you can do it without looking.
- ☐ I can import a module and call its functions with dot notation.
- ☐ I can explain
import math,from math import sqrtandimport math as m, and the trade-offs. - ☐ I can use
sqrt,pi,floor,ceilandpow, and predict results for negative numbers. - ☐ I can use
random,randint,randrange,choice,shuffleandseed, and say which include their end value. - ☐ I can write my own module, import it, and guard its test code with
if __name__ == "__main__":. - ☐ I can read
NameError,ModuleNotFoundErrorandAttributeErrormessages from imports and fix their causes.
Test yourself in the free Kestrel Exams app
12 practice questions on import, math, random, your own modules and __name__. Free, offline, no account.
Frequently asked questions#
What is a module in Python?
A module is a Python file containing functions, values and other definitions that a program can load with import. Python comes with many standard modules, such as math and random, and any .py file you write whose name is a valid Python name (letters, digits and underscores, not starting with a digit) can be imported as a module by programs in the same folder.
What is the difference between import math and from math import sqrt?
import math loads the module and you write math.sqrt(16), which makes it clear where each name comes from. from math import sqrt still loads the whole module but makes just that name available in your program, so you can write sqrt(16), which is shorter but can clash with your own names.
Does random.randint include the upper bound?
Yes. random.randint(a, b) returns a whole number from a to b including both ends, so randint(1, 6) can return 6. random.randrange(a, b) stops before b, like range.
What does random.seed do?
It sets the starting point of the random number generator. With the same seed, the same sequence of "random" numbers is produced every run, which makes programs that use randomness repeatable for testing.
What does if __name__ == "__main__" mean?
Python sets __name__ to "__main__" in the file being run directly, and to the module's name in a file that is imported. Code under this if therefore runs only when the file is run on its own, not when another program imports it.
Why do I get AttributeError: module random has no attribute randint?
Usually because a file of your own named random.py is in the same folder, so import random loads your file instead of Python's module. Rename your file and delete any leftover random.py.
Something here not clear? A topic you wish we covered? Tell us. We read every message, and a request is the fastest way to get a guide written — several of these exist because somebody asked.