All 57 lessons

Lesson 24 of 57 · Files, Errors and Modules

Python Modules and the Standard Library: datetime, math, random and More

By Skillancy Editorial Team

Run the code in the free Python Compiler or on your own computer. Lessons that read files use the FreshBasket practice data: download the dataset (ZIP).

Python's famous slogan is "batteries included". Out of the box you get tools for dates, statistics, random sampling, compression, regular expressions, JSON, CSV and much more, all organised into modules. A module is simply a file of Python code that you can bring into your program with import. Learning to use the standard library well is one of the fastest ways to become productive, because you stop reinventing things that already exist, are well tested, and are known to every other Python programmer.

This lesson first explains how imports work and how to create a module of your own (we will put the lakh/crore formatter into one). Then it takes a guided tour of the modules analysts reach for most often. You will calculate delivery timestamps with datetime, summarise numbers with statistics, sample data reproducibly with random, and combine collections with itertools.

Worked examples

Example 1: Ways to import

Question: What are the different import styles?

import math
from math import sqrt, pi
import statistics as st
from datetime import date as Date

print(math.floor(7.8), sqrt(49), round(pi, 4))
print(st.mean([4, 8, 6]))
print(Date(2026, 6, 30))
print(math.__name__, type(math).__name__)
print([n for n in dir(math) if n.startswith("lo")])

Output

7 7.0 3.1416
6
2026-06-30
math module
['log', 'log10', 'log1p', 'log2']

How it works: import math makes the module available as math, and you reach its contents with a dot. from math import sqrt copies chosen names into your namespace. as gives a shorter alias, which is a strong convention for some libraries (import pandas as pd). dir(module) lists everything a module offers.

Reading the result: The floor of 7.8 is 7 and sqrt(49) is 7.0. Avoid from module import * because it dumps unknown names into your code and can overwrite your own.

Example 2: Create your own module

Question: Move the Indian money formatter into a reusable file.

from pathlib import Path

Path("fbtools.py").write_text('''"""Small helpers for FreshBasket analysis."""

def indian_commas(n):
    s = str(int(n))
    if len(s) <= 3:
        return s
    head, tail = s[:-3], s[-3:]
    groups = []
    while len(head) > 2:
        groups.insert(0, head[-2:])
        head = head[:-2]
    if head:
        groups.insert(0, head)
    return ",".join(groups + [tail])

def inr(amount):
    sign = "-" if amount < 0 else ""
    return f"{sign}₹{indian_commas(abs(amount))}"

if __name__ == "__main__":
    print("self-test:", inr(1234567))
''', encoding="utf-8")

import fbtools
print(fbtools.inr(98765432))
print(fbtools.__doc__)
print(fbtools.__name__)

Output

₹9,87,65,432
Small helpers for FreshBasket analysis.
fbtools

How it works: Any .py file is a module, and its name (without .py) is the import name. We wrote one with the Path tool from the files lesson, then imported it. The special variable __name__ is "__main__" when you run a file directly and the module name when it is imported, so the code under if __name__ == "__main__": runs only for direct runs, which makes it a good place for quick self-tests.

Reading the result: The imported function printed ₹9,87,65,432, and the module's __name__ is fbtools, not __main__, so the self-test did not run on import. Python finds your module because the current folder is on its search path.

Example 3: datetime basics

Question: Build dates and times and extract their parts.

from datetime import date, datetime, time

d = date(2026, 6, 30)
print(d, d.year, d.month, d.day, d.weekday(), d.strftime("%A"))

dt = datetime(2026, 6, 30, 18, 45)
print(dt, dt.hour, dt.minute)
print(dt.date(), dt.time())

print(date.fromisoformat("2025-12-31"))
print(datetime.strptime("30/06/2026 07:15 PM", "%d/%m/%Y %I:%M %p"))
print(dt.strftime("%d %b %Y, %I:%M %p"))

Output

2026-06-30 2026 6 30 1 Tuesday
2026-06-30 18:45:00 18 45
2026-06-30 18:45:00
2025-12-31
2026-06-30 19:15:00
30 Jun 2026, 06:45 PM

How it works: date holds a calendar day and datetime adds the time of day. weekday() counts Monday as 0. strftime formats a date as text and strptime parses text into a date; both use codes like %d (day), %m (month), %Y (four-digit year), %H/%I (24/12 hour), %M (minute) and %p (AM/PM). Memorising these few codes pays off for years.

Reading the result: 30 June 2026 is a Tuesday (weekday number 1). The parsed text 30/06/2026 07:15 PM became 2026-06-30 19:15:00.

Example 4: Date arithmetic

Question: How many days are between two dates, and what is the date 45 days later?

from datetime import date, timedelta

start = date(2025, 1, 1)
end = date(2026, 6, 30)

gap = end - start
print(gap, gap.days)
print(start + timedelta(days=45))
print(end - timedelta(weeks=2))

delivery_time = timedelta(minutes=28)
from datetime import datetime
placed = datetime(2026, 6, 30, 18, 45)
print(placed + delivery_time)

print(max(date(2026, 1, 5), date(2025, 12, 1)))
print((end.replace(day=1) - timedelta(days=1)).isoformat())

Output

545 days, 0:00:00 545
2025-02-15
2026-06-16
2026-06-30 19:13:00
2026-01-05
2026-05-31

How it works: Subtracting two dates gives a timedelta, which stores days (and seconds). Adding a timedelta to a date or datetime shifts it. Dates compare naturally with <, > and max. The last line finds the last day of the previous month by going to the first of the month and stepping back one day, a classic trick.

Reading the result: The orders data spans 545 days. An order placed at 18:45 with a 28-minute delivery arrives at 19:13.

Example 5: Real order dates

Question: Find the first and last order dates and the busiest weekday.

import csv
from collections import Counter
from datetime import date

dates = []
with open("orders.csv", newline="", encoding="utf-8") as f:
    for row in csv.DictReader(f):
        dates.append(date.fromisoformat(row["order_date"]))

print(min(dates), max(dates))
by_weekday = Counter(d.strftime("%A") for d in dates)
print(by_weekday.most_common(3))
print((max(dates) - min(dates)).days + 1)

Output

2025-01-01 2026-06-30
[('Sunday', 1079), ('Saturday', 1042), ('Monday', 796)]
546

How it works: We convert each ISO text date (2025-01-01) with date.fromisoformat and then use min, max, a Counter on weekday names and date subtraction. Pandas does all of this faster with pd.to_datetime, but the logic is identical.

Reading the result: Orders run from 2025-01-01 to 2026-06-30, which is 546 days, and Sunday is the busiest weekday with 1,079 orders.

Example 6: math and statistics

Question: Summarise the delivery times with standard tools.

import math
import statistics as st

minutes = []
with open("orders.csv", newline="", encoding="utf-8") as f:
    for row in csv.DictReader(f):
        if row["delivery_minutes"]:
            minutes.append(float(row["delivery_minutes"]))

print(len(minutes))
print(round(st.mean(minutes), 2), st.median(minutes), st.mode([int(m) for m in minutes]))
print(round(st.stdev(minutes), 2))
print(math.ceil(max(minutes)), math.isclose(0.1 + 0.2, 0.3))
print(st.quantiles(minutes, n=4))

Output

5630
23.67 24.0 24
6.93
54 True
[19.0, 24.0, 28.0]

How it works: statistics offers mean, median, mode, stdev and quantiles. math.ceil rounds up, and math.isclose compares floats safely, since 0.1 + 0.2 is not exactly 0.3 in binary. For large arrays NumPy is faster, but these functions are perfect for small lists and need no installation.

Reading the result: The average delivery takes 23.7 minutes against a median of 24, with a standard deviation of 6.9. The three quartile cut points are [19.0, 24.0, 28.0].

Example 7: random with a seed

Question: Take a reproducible random sample.

import random

random.seed(42)
print(random.random() < 1)
print(random.randint(1, 6))

ids = [f"C{i:04d}" for i in range(1, 601)]
sample = random.sample(ids, 5)
print(sample)

random.seed(42)
a = random.sample(ids, 5)
random.seed(42)
b = random.sample(ids, 5)
print(a == b)

print(random.choice(["UPI", "Card", "COD"]))
print(random.choices(["UPI", "Card"], weights=[8, 2], k=5))
deck = [1, 2, 3, 4, 5]
random.shuffle(deck)
print(sorted(deck))

Output

True
1
['C0282', 'C0251', 'C0229', 'C0143', 'C0105']
True
UPI
['UPI', 'UPI', 'Card', 'UPI', 'UPI']
[1, 2, 3, 4, 5]

How it works: Computers produce pseudo-random numbers from a starting seed. Setting random.seed(42) makes the sequence repeat, which makes experiments and tutorials reproducible. sample picks without replacement, choices picks with replacement (and optional weights), choice picks one, and shuffle reorders in place.

Reading the result: The same seed gave the same sample both times (True). In analysis, always set a seed when you sample or split data, so a colleague gets the same result.

Example 8: collections beyond Counter

Question: Use namedtuple and deque.

from collections import namedtuple, deque, Counter

Store = namedtuple("Store", ["store_id", "city", "orders"])
s = Store("S01", "Bengaluru", 1301)
print(s, s.city, s[2])
print(s._replace(orders=1400))
print(s._asdict())

last3 = deque(maxlen=3)
for minutes_taken in [12, 18, 25, 31, 22]:
    last3.append(minutes_taken)
print(list(last3), sum(last3) / len(last3))

c1 = Counter(a=3, b=1)
c2 = Counter(a=1, b=4)
print(c1 + c2, c2 - c1)

Output

Store(store_id='S01', city='Bengaluru', orders=1301) Bengaluru 1301
Store(store_id='S01', city='Bengaluru', orders=1400)
{'store_id': 'S01', 'city': 'Bengaluru', 'orders': 1301}
[25, 31, 22] 26.0
Counter({'b': 5, 'a': 4}) Counter({'b': 3})

How it works: A namedtuple is a lightweight tuple whose fields also have names. A deque with maxlen keeps only the latest items, which makes it ideal for rolling windows. Counters support arithmetic: + adds counts, - subtracts and drops results at zero or below.

Reading the result: The rolling window kept [25, 31, 22] with a moving average of 26.0. Namedtuples give tuple efficiency with readable access: s.city is Bengaluru.

Example 9: itertools

Question: Generate combinations and run totals without loops.

from itertools import chain, product, combinations, accumulate, groupby

print(list(chain([1, 2], [3], [4, 5])))
print(list(product(["S01", "S02"], ["AM", "PM"])))
print(list(combinations(["Milk", "Eggs", "Bread"], 2)))
print(list(accumulate([100, 250, 175, 300])))

records = [("Pune", 450), ("Pune", 300), ("Delhi", 1200), ("Delhi", 150), ("Pune", 80)]
for city, grp in groupby(sorted(records), key=lambda r: r[0]):
    print(city, sum(a for _, a in grp))

Output

[1, 2, 3, 4, 5]
[('S01', 'AM'), ('S01', 'PM'), ('S02', 'AM'), ('S02', 'PM')]
[('Milk', 'Eggs'), ('Milk', 'Bread'), ('Eggs', 'Bread')]
[100, 350, 525, 825]
Delhi 1350
Pune 830

How it works: chain joins iterables, product gives every pairing (a Cartesian product), combinations gives unordered pairs, and accumulate gives running totals. groupby groups adjacent equal keys, so you must sort first; forgetting this is the classic mistake.

Reading the result: Three products make 3 possible pairs, which is how basket analysis counts item combinations. The running totals ended at 825.

Example 10: Finding more

Question: How do you discover what a module can do?

import textwrap
import statistics

print(statistics.__doc__.strip().splitlines()[0])
print(statistics.median.__doc__.strip().splitlines()[0])
print(callable(statistics.mean))
print(textwrap.shorten("Python's standard library is vast and well documented online", width=40, placeholder="..."))

import sys
print(sys.version_info.major, sys.platform in ("linux", "darwin", "win32"))
print(any("site-packages" in p or "dist-packages" in p for p in sys.path))

Output

Basic statistics module.
Return the median (middle value) of numeric data.
True
Python's standard library is vast and...
3 True
True

How it works: Every module and function carries a docstring, available through help(), the __doc__ attribute, or the editor tooltip. sys.path shows the folders Python searches for imports; third-party packages installed with pip install land in site-packages. The official documentation at docs.python.org lists every standard module.

Reading the result: The statistics module describes itself as "Basic statistics module.". Get into the habit of reading a docstring before searching the web.

Common mistakes

1. Naming your file like a standard module. A file called random.py or statistics.py will shadow the real module.

2. Circular imports. Module A imports B which imports A; restructure shared code into a third module.

*3. Using `from x import `.**

4. Forgetting that groupby needs sorted data.

5. Mixing naive and aware datetimes.

from datetime import datetime, timezone
datetime(2026, 1, 1) - datetime(2026, 1, 1, tzinfo=timezone.utc)

Output

Traceback (most recent call last):
  File "example.py", line 2, in <module>
    datetime(2026, 1, 1) - datetime(2026, 1, 1, tzinfo=timezone.utc)
    ~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
TypeError: can't subtract offset-naive and offset-aware datetimes

Practice questions

Practice 1 (Easy): Days until a date

Print how many days remain from 30 June 2026 until 31 December 2026.

Show solution
from datetime import date
print((date(2026, 12, 31) - date(2026, 6, 30)).days)

Output

184

Subtracting dates gives a timedelta, and .days is the whole-day count, 184.

Practice 2 (Medium): Weekend orders

Using the dates list from earlier, compute the share of orders placed on Saturday or Sunday.

Show solution
weekend = sum(d.weekday() >= 5 for d in dates)
print(f"{weekend / len(dates):.1%}")

Output

35.4%

Weekday numbers 5 and 6 are Saturday and Sunday. Weekend orders were 2121 of 6,000, a share of 35.4%.

Practice 3 (Hard): Reproducible train/test split

Shuffle the first 20 numbers using a seeded random.Random object, then use the first 15 as train and the rest as test, and prove the split is the same on a second run.

Show solution
import random

def split(seed):
    rng = random.Random(seed)
    items = list(range(1, 21))
    rng.shuffle(items)
    return items[:15], items[15:]

train1, test1 = split(7)
train2, test2 = split(7)
print(test1, test1 == test2, sorted(train1 + test1) == list(range(1, 21)))

Output

[3, 2, 13, 5, 11] True True

A private Random object keeps the seed separate from the global generator. The split is repeatable: True, and nothing is lost or duplicated.

Key takeaways

  • A module is a .py file; import brings it in, and as shortens its name.
  • Put reusable functions in your own module, and guard demo code with if __name__ == "__main__":.
  • datetime handles dates, differences (timedelta), parsing (strptime) and formatting (strftime).
  • statistics and math cover everyday numeric summaries; use isclose for floats.
  • Seed random for reproducible sampling.
  • collections and itertools replace many hand-written loops.
  • Read docstrings and the official docs before reaching for the web.

Frequently asked questions

What is the difference between a module, a package and a library?

A module is one .py file. A package is a folder of modules (with an __init__.py). "Library" is an informal word for a collection of reusable code, such as pandas.

Where does Python look for modules?

In the folder of the running script, then in the standard library, then in installed packages (site-packages). sys.path holds the full list.

Why use if __name__ == "__main__":?

It lets one file work as both a reusable module and a runnable script. Code under the guard does not run when the file is imported.

Should I use datetime or pandas for dates?

Use datetime for single values and small scripts, and pandas (pd.to_datetime, .dt accessor) for whole columns. The format codes are the same.

How do I handle time zones?

Use aware datetimes with zoneinfo.ZoneInfo("Asia/Kolkata"). Store times in UTC and convert for display.

How do I install third-party packages?

Use pip install package-name in your terminal, preferably inside a virtual environment, as covered in the setup lesson.

Newsletter

Keep learning Python

One email a week with Python and analytics guides, new practice questions and cohort dates. No spam, unsubscribe anytime.

Ready to practise? Try the Python interview questions