Modules and the standard library

Python comes with a huge collection of tools ready to use: randomness, maths, dates, file paths, and much more. These tools live in modules, and you bring them into your code with import. You have already used import json in the Files and exceptions chapter. This chapter covers imports fully and introduces the most useful parts of the standard library.
Importing modules
The simplest import brings in a whole module and lets you use its contents with dot notation. You can also import specific names from a module to use them directly without the prefix. Aliases shorten long names.
import math
math.sqrt(16) # 4.0
math.pi # 3.141592653589793
math.floor(3.9) # 3
math.ceil(3.1) # 4Import specific names from a module so you can use them directly:
from math import sqrt, pi
sqrt(16) # 4.0 (no "math." prefix needed)
pi # 3.141592653589793Give a module or name an alias to shorten it:
import math as m
m.sqrt(16) # 4.0
from math import sqrt as square_root
square_root(25) # 5.0Aliases are common with popular third-party libraries (import numpy as np, import pandas as pd). For standard library modules, prefer using the full name; it makes the code more readable.
import math brings in the whole module, then you reach inside with a dot: math.sqrt(16). from math import sqrt grabs one name so you can drop the prefix. The as keyword renames a long import, but for standard-library stuff the full name reads clearer. random
The random module generates random numbers and makes random choices. Use it for games, simulations, random sampling, and anything else that needs unpredictability. Setting a seed makes results reproducible: the same seed produces the same sequence every time.
import random
random.random() # float between 0 and 1 (exclusive)
random.randint(1, 10) # integer from 1 to 10 (both inclusive)
random.uniform(1.0, 10.0) # float between 1.0 and 10.0
colours = ["red", "green", "blue"]
random.choice(colours) # picks one item
random.choices(colours, k=3) # picks k items (with replacement)
random.sample(colours, k=2) # picks k items (no replacement)
numbers = [1, 2, 3, 4, 5]
random.shuffle(numbers) # shuffles in place, returns NoneFor reproducible results (useful in testing and data science), set a seed before generating:
random.seed(42)
random.randint(1, 100) # always the same value for seed 42The same seed produces the same sequence every time, on any machine.
random.choice() picks one item from a list, random.randint(1, 10) gives a whole number in a range, end included. Want the same results every run, say for a test? Call random.seed() first and the sequence locks in. math
The math module adds more advanced mathematical operations beyond the basic arithmetic operators you met in the Numbers chapter. Square roots, powers, logarithms, trigonometry, and special values like pi and infinity are all here.
import math
math.sqrt(25) # 5.0
math.pow(2, 10) # 1024.0 (same as 2 ** 10 but always returns float)
math.log(100, 10) # 2.0 (log base 10)
math.log(math.e) # 1.0 (natural log)
math.sin(math.pi / 2) # 1.0
math.cos(0) # 1.0
math.ceil(3.2) # 4
math.floor(3.9) # 3
math.trunc(3.9) # 3 (same as int() for positives)
math.inf # infinity
math.isnan(float("nan")) # True
math.isinf(math.inf) # Truemath covers everything past + - * /: square roots, powers, logs, trig, plus constants like math.pi. Reach for it the moment a calculation needs more than the basic operators, and you skip writing the formula yourself. datetime
The datetime module handles dates and times. datetime.now() gives you the current date and time. strftime() formats it as a string. strptime() parses a string into a datetime. timedelta represents a duration you can add or subtract.
from datetime import datetime, date, timedelta
now = datetime.now() # current date and time
today = date.today() # current date only
print(now.year, now.month, now.day)
print(now.hour, now.minute, now.second)
# Formatting
print(now.strftime("%Y-%m-%d")) # "2024-01-15"
print(now.strftime("%d %B %Y, %H:%M")) # "15 January 2024, 09:42"
# Parsing
deadline = datetime.strptime("2024-12-31", "%Y-%m-%d")
# Arithmetic
tomorrow = today + timedelta(days=1)
next_week = today + timedelta(weeks=1)
diff = deadline - now
print(f"{diff.days} days until deadline")Common strftime codes:
| Code | Meaning | Example |
|---|---|---|
%Y | 4-digit year | 2024 |
%m | Month (zero-padded) | 01 |
%d | Day (zero-padded) | 15 |
%H | Hour (24h) | 09 |
%M | Minute | 42 |
%B | Full month name | January |
datetime.now() gives you the current date and time. strftime() turns it into a string with codes like %Y-%m-%d, and strptime() goes the other way, parsing a string back. timedelta is a duration you can add or subtract, so today + timedelta(days=1) is tomorrow. os and pathlib
pathlib is the modern way to work with file paths. Path objects let you build, inspect, and navigate paths using the / operator. os gives access to environment variables and lower-level OS operations. Prefer pathlib for new code.
from pathlib import Path
p = Path("data/reports")
p.exists() # True if path exists
p.is_dir() # True if it's a directory
p.is_file() # True if it's a file
p.mkdir(parents=True, exist_ok=True) # create directories
for f in p.glob("*.csv"): # all CSV files in directory
print(f.name) # the filename
report = p / "report_jan.csv" # / operator joins paths
report.stem # "report_jan" (name without extension)
report.suffix # ".csv"
report.parent # Path("data/reports")
content = report.read_text() # read file contents directly
report.write_text("new content\n") # write directlyFor the os module:
import os
os.getcwd() # current working directory
os.listdir(".") # list directory contents
os.path.exists("data.txt") # True if path exists
os.path.join("data", "file.txt") # "data/file.txt" (cross-platform)
os.environ.get("HOME") # read an environment variablePrefer pathlib for new code. Use os when you need environment variables or working with older APIs that expect strings.
pathlib.Path treats a file path as an object you can poke at: .exists(), .read_text(), .write_text(), .glob(). The / operator joins pieces, so p / "report.csv" reads like a real path. Reach for pathlib over the older os.path in new code. timeit
timeit measures how long code takes to run. It is useful when you want to compare two approaches and pick the faster one. Run the code many times to get a stable measurement.
import timeit
# Time a single statement
timeit.timeit("sum(range(1000))", number=10000)
# Time a more complex block
setup = "data = list(range(1000))"
code = "[x * 2 for x in data]"
time = timeit.timeit(code, setup=setup, number=10000)
print(f"{time:.4f} seconds for 10,000 runs")number is how many times to repeat. More repetitions give a more stable measurement.
timeit.timeit() times a piece of code by running it over and over, then you divide by the run count for a steady per-call number. It's how you settle "which of these two is faster" instead of guessing. One run is too noisy to trust, so let it loop. string
The string module provides pre-built string constants for letters, digits, and punctuation. Useful when you need to check characters or generate random strings from a specific alphabet.
import string
string.ascii_lowercase # "abcdefghijklmnopqrstuvwxyz"
string.ascii_uppercase # "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
string.ascii_letters # both combined
string.digits # "0123456789"
string.punctuation # all punctuation charactersUseful when you need to check characters or generate random strings:
import string, random
chars = string.ascii_letters + string.digits
password = "".join(random.choices(chars, k=12))string module hands you ready-made character sets: string.ascii_letters, string.digits, string.punctuation. They're plain strings, so you can loop over them or sample from them. Handy whenever you need a known alphabet to build or check against. Creating your own modules
Any Python file is a module. To use it from another file, import it by the filename (without .py). You can import the whole module and use its contents with dot notation, or import specific names directly.
# utils.py
def clamp(value, lo, hi):
return max(lo, min(value, hi))
PI = 3.14159# main.py
import utils
utils.clamp(150, 0, 100) # 100
utils.PI # 3.14159
from utils import clamp
clamp(50, 0, 100) # 50Python finds the module by looking in the same directory as the importing file (and a few other places). For larger projects, modules are organised into packages: directories with an __init__.py file.
.py file is already a module: import it by its filename without the .py, and its functions and variables are yours to use. import utils then utils.clamp(...), or pull one name with from utils import clamp. That's how you split a growing program across files. __name__ == "__main__"
When Python runs a file directly, __name__ is set to "__main__". When the same file is imported as a module, __name__ is the module name. This pattern lets you write code that runs when you execute the file directly but is skipped when the file is imported by another module.
# utils.py
def clamp(value, lo, hi):
return max(lo, min(value, hi))
if __name__ == "__main__":
# this only runs when you do: python utils.py
# not when you do: import utils
print(clamp(150, 0, 100)) # 100This is a standard pattern for any module that is also useful as a standalone script.
if __name__ == "__main__": lets one file be both an importable module and a runnable script. Code under the guard runs when you do python utils.py, and is skipped when another file does import utils. Handy for tucking a quick demo or test at the bottom of a module. Standard library highlights
A few more modules worth knowing about. Each one solves a common problem that would take significant work to implement yourself.
collections: specialised container types:
from collections import Counter, defaultdict, deque
Counter(["a", "b", "a", "c", "a"]) # Counter({'a': 3, 'b': 1, 'c': 1})
defaultdict(list) # dict that auto-creates missing keys
deque([1, 2, 3], maxlen=5) # fast append/pop from both endsitertools: tools for working with iterables:
import itertools
list(itertools.chain([1, 2], [3, 4])) # [1, 2, 3, 4]
list(itertools.islice(range(100), 5)) # [0, 1, 2, 3, 4]
list(itertools.combinations([1, 2, 3], 2)) # [(1, 2), (1, 3), (2, 3)]
list(itertools.product([0, 1], repeat=2)) # [(0,0), (0,1), (1,0), (1,1)]sys: access to the Python interpreter:
import sys
sys.argv # list of command-line arguments
sys.exit(1) # exit with a status code
sys.version # Python version stringThird-party packages: beyond the standard library, pip installs community packages:
pip install requests # HTTP library
pip install pandas # data manipulation
pip install numpy # numerical computingThird-party packages are out of scope for this guide, but the pattern is always the same: pip install, then import.
In practice
Combining random, string, and datetime to generate unique game IDs with timestamps:
import random
import string
from datetime import datetime
def generate_game_id(length: int = 8) -> str:
chars = string.ascii_uppercase + string.digits
return "".join(random.choices(chars, k=length))
def timestamp() -> str:
return datetime.now().strftime("%Y-%m-%d %H:%M:%S")
game_id = generate_game_id()
print(f"[{timestamp()}] Starting game {game_id}")
scores = [random.randint(50, 100) for _ in range(5)]
print(f"Round scores: {scores}")
print(f"Best: {max(scores)}")
