In the previous lesson you learned to import modules and created your own. Now it's time to open the gift that comes with every Python installation: the standard library, more than two hundred ready-made, tested, documented modules covering math, randomness, dates, text, files and much more — nothing to install, no pip, no .venv to touch. Pythonistas call it the "batteries included" philosophy: before programming something, ask whether the standard library already ships it. This lesson is a guided tour, not an encyclopedia: you'll see the modules you'll use most, with short examples applied to Papyrus, and a map of which ones we'll revisit in depth during the rest of the course. We'll close the module with the definitive (for now) version of menu.py, rewritten on top of papyrus_utils.

Contents

  1. The "batteries included" philosophy
  2. math: mathematics beyond the operators
  3. random: randomness for raffles and test data
  4. datetime: dates and times for the orders
  5. string and textwrap: text utilities
  6. statistics: basic statistics without installing anything
  7. sys and os: talking to the system
  8. Map of the standard library in this course
  9. Closing the module: menu.py reorganized

The "batteries included" philosophy

Everything you saw in 03-04 applies as is: import math works exactly like import papyrus_utils, except the file lives in the standard library folders of sys.path instead of in your project. The official documentation (docs.python.org, Library Reference section) is the canonical reference; help(module) in the REPL is the quick shortcut. The skill to train isn't memorizing functions, but knowing that the module exists: whoever knows statistics exists doesn't reimplement the mean with a loop.

math: mathematics beyond the operators

The operators (+, **, //...) cover the basics; math supplies the rest:

import math

print(math.sqrt(16))        # 4.0  square root
print(math.ceil(2.04))      # 3    ALWAYS rounds up
print(math.floor(9.83))     # 9    ALWAYS rounds down
print(math.pi)              # 3.141592653589793

A shop example: books arrive in boxes of 6 units. If Ana needs 20 copies, how many boxes does she order?

boxes = math.ceil(20 / 6)
print(f"Order: {boxes} boxes")   # 4 (3.33 boxes isn't enough: you round up)

Watch the difference from round(), which you already know: round(3.33) gives 3 (to the nearest), math.ceil(3.33) gives 4 (ceiling). For "how many containers do I need?", always ceil.

random: randomness for raffles and test data

import random

print(random.randint(1, 6))            # integer between 1 and 6, BOTH included
print(random.choice(catalog))          # one random element from the list
print(random.sample(catalog, 2))       # 2 distinct elements, no repeats
print(round(random.uniform(5, 25), 2)) # random float between 5 and 25

At Papyrus, random.choice() powers the "surprise recommendation of the day", and random.sample() a raffle among members without repeating a winner. Its other big professional use: generating test data (prices, stock levels) to rehearse programs before you have real data. And a lab trick: random.seed(42) pins the generator's seed so the "random" sequence is reproducible — essential for debugging and, later on, for the tests in module 9.

datetime: dates and times for the orders

The datetime module contains (among others) the classes date and datetime — yes, module and class share a name, the source of endless confusion. The usual thing is to import the classes directly:

from datetime import date, datetime, timedelta

today = date.today()
now = datetime.now()
print(today)                            # 2026-07-13
print(f"{now:%d/%m/%Y %H:%M}")          # 13/07/2026 10:45  (custom format)

# The "Faust" order arrives in 7 days:
arrival = today + timedelta(days=7)
print(f"The Faust order will arrive on {arrival:%d/%m/%Y}")

# How many days has Luis been waiting? (subtracting dates gives a timedelta)
ordered = date(2026, 7, 1)
print(f"Days waiting: {(today - ordered).days}")    # 12

Three key ideas: date objects can be operated on (adding a timedelta, subtracting two dates), they're formatted in f-strings with the codes %d, %m, %Y, %H, %M, and they're constructed with date(year, month, day). With this, Papyrus can date receipts and calculate deliveries; when the shop stores orders in files (module 6), dates will travel as text and datetime will parse them back.

string and textwrap: text utilities

Two small but handy modules. string offers ready-made constants for validation:

import string

print(string.ascii_lowercase)   # abcdefghijklmnopqrstuvwxyz
print(string.digits)            # 0123456789
print(string.punctuation)       # !"#$%&'()*+,-./:;<=>?@[\]^_`{|}~

For example, to check that a discount code uses only uppercase letters and digits: all(c in string.ascii_uppercase + string.digits for c in code).

textwrap fits paragraphs to a given width — perfect so a book's synopsis fits on Papyrus's 40-column receipt:

import textwrap

synopsis = ("Faust, a dissatisfied scholar, strikes a deal with "
            "Mephistopheles: youth and knowledge in exchange for his soul.")
print(textwrap.fill(synopsis, width=40))
Faust, a dissatisfied scholar, strikes a
deal with Mephistopheles: youth and
knowledge in exchange for his soul.

statistics: basic statistics without installing anything

Before reaching pandas (module 11), everyday statistics already come included:

import statistics

prices = [12.50, 9.95, 15.90, 21.00]
print(statistics.mean(prices))     # 14.8375  mean
print(statistics.median(prices))   # 14.2     median

With mean() Ana answers "what's the average price of my catalog?" in one line. The median holds up better against extreme values: if a 300 EUR facsimile came in, the mean would shoot up while the median would barely move.

sys and os: talking to the system

From sys you already know sys.path (lesson 03-04). Its other star is sys.argv: the list of command-line arguments the script was run with — the script-level equivalent of a function's parameters. sys.argv[0] is the name of the script itself, and the rest is whatever was typed after it:

# lookup.py — usage: python lookup.py <title>
import sys

from papyrus_utils import find_book

catalog = ["The Odyssey", "Hamlet", "Don Quixote"]

if len(sys.argv) < 2:
    print("Usage: python lookup.py <title>")
else:
    index = find_book(catalog, sys.argv[1])
    print("In catalog" if index is not None else "Not available")
> python lookup.py Hamlet
In catalog

This way Ana checks without going through the menu: "one-shot" scripts for quick tasks. Careful: the arguments always arrive as strings, just like with input().

For its part, os (and its modern companion pathlib) communicates with the operating system: os.getcwd() tells you which folder you're in, os.listdir() lists files, and onward from there — creating folders, checking whether a file exists, deleting. This is just an introduction: it stars in module 6, when the catalog lives in files.

Map of the standard library in this course

Module What it's for Where it's used in this course
math Roots, ceilings/floors, constants From now on, wherever needed
random Randomness, sampling, test data Here; sample data in modules 4-6
datetime Dates, times, time arithmetic Here; order dates in module 6 (CSV/JSON)
string / textwrap Text constants / tailored paragraphs Useful curiosities; strings in depth in 04-05
statistics Mean, median and company Here; a prelude to pandas (module 11)
sys argv, path, interpreter exit Here and in debugging (module 9)
os / pathlib Files, folders, paths Module 6
collections Extra data structures Module 4 (04-06)
csv / json Reading and writing structured data Module 6
logging Professional event logging Module 7
unittest / pdb Testing and debugging Module 9
asyncio / threading Concurrency Module 8

Keep this table as a compass: much of the syllabus ahead of you is, at heart, learning to use the standard library well.

Closing the module: menu.py reorganized

The promise from module 2 is settled. The final version of menu.py imports papyrus_utils (lesson 03-04), splits the work into functions with one responsibility each (03-01), uses keyword arguments (03-02) and even dates the session with datetime:

"""Management menu for the Papyrus bookshop."""

from datetime import date

from papyrus_utils import STORE_NAME, find_book, final_price

catalog = ["The Odyssey", "Hamlet", "Don Quixote"]
prices = [12.50, 9.95, 15.90]
stocks = [4, 6, 8]


def show_catalog():
    """Print the table of titles, prices and stock."""
    print(f"\n{'Title':<12} {'Price':>8} {'Stock':>6}")
    print("-" * 28)
    for title, price, stock in zip(catalog, prices, stocks):
        print(f"{title:<12} {price:>6.2f} EUR {stock:>6}")


def look_up_book():
    """Ask for a title and show its card, member price included."""
    wanted = input("Title to search for: ")
    if not wanted:
        print("You didn't type anything.")
        return
    index = find_book(catalog, wanted)
    if index is None:
        print(f"'{wanted}' is not in the catalog.")
        return
    base = prices[index]
    print(f"'{catalog[index]}': {base:.2f} EUR "
          f"(members: {final_price(base, member=True):.2f} EUR), stock {stocks[index]}.")


def run_menu():
    """Main loop of the shop menu."""
    print(f"{STORE_NAME} management — {date.today():%d/%m/%Y}")
    while True:
        print("\n1. View catalog\n2. Look up a book\n3. Exit")
        match input("Choose an option (1-3): "):
            case "1":
                show_catalog()
            case "2":
                look_up_book()
            case "3":
                print(f"Closing {STORE_NAME}. See you tomorrow, Ana!")
                break
            case _:
                print("Invalid option. Choose 1, 2 or 3.")


if __name__ == "__main__":
    run_menu()

Compare it with the monolithic version from module 2: it does the same and a bit more (the date, the member price on the card), but now every piece has a name, a docstring and a place. look_up_book() uses return as a guard — its early exits replace the continue of the old version. The business logic lives in papyrus_utils (reusable from receipt.py or member_sale.py), the presentation lives here, and the __main__ block lets another script do from menu import show_catalog without launching the whole menu. This is no longer a script: it's a small, well-organized program.

Common Mistakes and Tips

  • Reinventing the wheel: writing your own mean, your own shuffle or your own line wrapping. Before programming a "generic" utility, search the documentation: it probably already exists, bug-free and faster.
  • import datetime and then using datetime.now(): NameError/AttributeError from confusing module and class. Either import datetime + datetime.datetime.now(), or from datetime import datetime + datetime.now(). Pick one style and be consistent.
  • Expecting sys.argv to bring numbers: everything arrives as strings; convert with int()/float() just like with input().
  • Forgetting that sys.argv[0] is the script: the first real argument is sys.argv[1], and you must check len(sys.argv) before using it.
  • Naming files after standard modules: a random.py or string.py of yours shadows the official one (lesson 03-04). The symptoms are absurd errors in modules that have "always worked".
  • Using random for anything security-related: for raffles and tests it's perfect; for passwords or tokens there's the secrets module.
  • Tip: spend five minutes skimming the index of the official Library Reference. Not to learn it, but so that "I think there's a module for this" becomes your first reflex.

Exercises

Exercise 1: the book club raffle

Write raffle.py: given members = ["Luis", "Marta", "Chen", "Aitana", "Pau"], use random.sample() to pick two distinct winners of a copy of "Faust" and print each one with the pick-up deadline: today plus 15 days, formatted dd/mm/yyyy (use date.today() and timedelta).

Exercise 2: express catalog report

Write report.py which, with prices = [12.50, 9.95, 15.90, 21.00], prints: the average price and the median (statistics module, both with :.2f), the highest and lowest price (built-in functions max()/min()), and how many boxes of 6 units Ana needs to store 31 books (math module). Finish with a separator line of dashes generated with "-" * 30.

Exercise 3: lookup with arguments and papyrus_utils

Extend this lesson's lookup.py: it must accept python lookup.py <title> [member]. If the title exists in the catalog, print its final price — the member price if the second argument is exactly member, the regular one otherwise — using final_price() from papyrus_utils. If it doesn't exist or the title is missing, print the appropriate notice. Try it with python lookup.py Hamlet member (it must give 9.83 EUR).

Solutions

Exercise 1:

import random
from datetime import date, timedelta

members = ["Luis", "Marta", "Chen", "Aitana", "Pau"]
winners = random.sample(members, 2)
deadline = date.today() + timedelta(days=15)

for name in winners:
    print(f"{name}: pick up your 'Faust' by {deadline:%d/%m/%Y}")

sample() guarantees two distinct names (with choice() twice, Luis could win both copies). The date is calculated once and reused.

Exercise 2:

import math
import statistics

prices = [12.50, 9.95, 15.90, 21.00]

print(f"Average price:  {statistics.mean(prices):.2f} EUR")   # 14.84 EUR
print(f"Median:         {statistics.median(prices):.2f} EUR") # 14.20 EUR
print(f"Most expensive: {max(prices):.2f} EUR")               # 21.00 EUR
print(f"Cheapest:       {min(prices):.2f} EUR")               # 9.95 EUR
print("-" * 30)
print(f"Boxes for 31 books: {math.ceil(31 / 6)}")             # 6

Notice the natural mix of built-in functions (max, min) and modules (statistics, math): the standard library begins where the built-ins end.

Exercise 3:

import sys

from papyrus_utils import find_book, final_price

catalog = ["The Odyssey", "Hamlet", "Don Quixote"]
prices = [12.50, 9.95, 15.90]

if len(sys.argv) < 2:
    print("Usage: python lookup.py <title> [member]")
else:
    index = find_book(catalog, sys.argv[1])
    if index is None:
        print(f"'{sys.argv[1]}' is not in the catalog.")
    else:
        is_member = len(sys.argv) > 2 and sys.argv[2] == "member"
        print(f"{catalog[index]}: {final_price(prices[index], member=is_member):.2f} EUR")

is_member takes advantage of the and short-circuit: if there's no third argument, it never tries to read sys.argv[2] (avoiding an IndexError). Note the imports in two PEP 8 blocks: standard (sys) on top, your own (papyrus_utils) below.

Conclusion

Module 3 closes the circle it opened: the Papyrus code no longer lives scattered around. You know how to define functions with def, docstring and return (03-01); how to design their signatures with positional and keyword arguments, default values, *args/**kwargs, and walk parallel lists with zip() (03-02); how to write throwaway lambdas as the criterion for sorted(), min() and max() (03-03); how to split code into modules of your own like papyrus_utils.py with if __name__ == "__main__" and packages with __init__.py (03-04); and how to find your way around the standard library with math, random, datetime, statistics and sys.argv (03-05). The new menu.py — small functions, imported business logic, separate presentation — is the proof of maturity. But a crack remains in plain sight: the catalog is three parallel lists that must be kept in sync by hand, and it's already forced us into juggling with zip() and indexes. Module 4 strikes exactly there: Python's data structures — lists in depth, tuples, dictionaries, sets and the gems of collections — with which every Papyrus book will at last be a single piece of information, not a row number spread across three lists.

Python Programming Course

Module 1: Introduction to Python

Module 2: Control Structures

Module 3: Functions and Modules

Module 4: Data Structures

Module 5: Object-Oriented Programming

Module 6: File Handling

Module 7: Error and Exception Handling

Module 8: Advanced Topics

Module 9: Testing and Debugging

Module 10: Web Development with Python

Module 11: Data Science with Python

Module 12: Final Project

© Copyright 2026. All rights reserved