Modules: math, random and friends

Part of the free LogicWiz Python course, Module 3: Functions. The examples and practice run in the app.

Every year the board paper shows random.randint(1, 3) inside a loop and asks which of four outputs is possible. Half the class loses those marks because they forget that randint(1, 3) can give 3 but randrange(1, 3) can't.

You'll learn to

  • Import a module three ways: `import`, `from … import`, `as`
  • Use `math`, `random`, `statistics` and `datetime`
  • Answer "which outputs are possible?" questions with a method
  • Know what `if __name__ == "__main__":` is for

Learn

A module is a file of ready-made functions

Python ships with hundreds of modules. Three ways to use one:

  • import math → call math.sqrt(16)
  • from math import sqrt → call sqrt(16) (only sqrt comes in, not math)
  • import statistics as st → call st.mean(…) (a short nickname)

Avoid from math import *: it dumps every name into your file and you can't tell where pow came from.

math

import math

print(math.sqrt(49))
print(math.ceil(4.1), math.floor(4.9))
print(math.ceil(-4.1), math.floor(-4.9))
print(math.pow(2, 3), 2 ** 3)
print(round(math.pi, 4))
print(math.fabs(-7), abs(-7))

from math import gcd, factorial
print(gcd(12, 18), factorial(5))

ceil goes up to the next whole number, floor goes down, even for negatives (floor(-4.9) is -5). sqrt, pow and fabs always give a float.

random: what each call can give

CallPossible values
random.randint(1, 6)1, 2, 3, 4, 5, 6 (both ends included)
random.randrange(1, 6)1, 2, 3, 4, 5 (stop excluded, like range)
random.randrange(10, 50, 10)10, 20, 30, 40
random.random()a float from 0.0 up to, but not including, 1.0
random.uniform(1, 5)a float between 1 and 5
random.choice("ABC")'A', 'B' or 'C'

Seeding makes "random" repeatable

import random

random.seed(7)
print(random.randint(1, 6))
print(random.randrange(10, 20, 5))
print(random.choice(["samosa", "vada pav", "poha"]))
print(round(random.random(), 3))

Run it twice: same output, because seed(7) restarts the same sequence. Delete the seed line and the output changes every run. Seeds are for testing; a real game wouldn't use one.

The method for "which outputs are possible?"

  1. For each random call, write its exact range (does it include the end?).
  2. If the call is inside a loop, the range may change each time round (randint(0, i)).
  3. Check each option value by value; one impossible value kills the option.

The board also asks for the minimum and maximum a variable can take. Same step 1.

statistics and datetime

import statistics as st

marks = [72, 85, 85, 90, 64]
print(st.mean(marks), st.median(marks), st.mode(marks))

from datetime import date

exam = date(2027, 3, 2)
today = date(2026, 10, 24)
print((exam - today).days, "days to the boards")
print(exam.strftime("%d-%m-%Y"), exam.year)

[72, 85, …] is a list of values; you'll meet lists properly in Module 5. Subtracting two dates gives a gap whose .days you can print.

__name__: am I being run or imported?

Every file has a hidden name __name__. When you run the file, it is "__main__". When another file imports it, it is the file's own name (for bill.py, "bill").

So put demo or test code under if __name__ == "__main__":. It runs when you run the file, and stays quiet when someone imports your functions.

The main guard

def gst(amount):
    return round(amount * 1.05, 2)

print("__name__ is", __name__)

if __name__ == "__main__":
    print("Testing gst:", gst(200))

Never name your own file random.py, math.py or statistics.py. Then import random finds your file instead of Python's, and you get AttributeError: module 'random' has no attribute 'randint'.

In one line

import m → m.f(), from m import f → f(); randint(a, b) includes b, randrange(a, b) doesn't; guard demo code with if __name__ == "__main__":.