Session 22 - Standard Library Tour

Session 22 of the Python Fundamentals series: the "batteries included" tour. It is a guided, runnable walk through the modules you reach for daily - math, with sqrt, floor, ceil, and pi; random, with randint, choice, shuffle, and seed for reproducibility; datetime, with date, timedelta, and strftime; pathlib.Path, with the / operator, name, suffix, and stem, and exists; and collections, with Counter and defaultdict - plus a look at os and sys. The traps are calling a function before you import its module, reinventing something the library already ships, and using random without a seed, so that results can never be reproduced. Every snippet and error message was executed and copied verbatim from CPython 3.12.

Subject: Python Fundamentals · 99 slides · code lesson

Open the interactive version of this deck · Homework for this lesson

What this lesson covers

The lesson, slide by slide

1. Standard Library Tour

Title

Python Fundamentals - Session 22

Batteries included: reach for the library before you write it yourself

2. What you will be able to do

Objectives

Python ships with a huge toolbox already installed. This session is a guided tour of the modules you will use every week. By the end you can:

  1. import a standard-library module and call its functions.
  2. Use math for sqrt, floor, ceil, and pi.
  3. Use random and set a seed so results are reproducible.
  1. Work with dates using date, timedelta, and strftime.
  2. Build and inspect paths with pathlib.Path.
  3. Tally things fast with Counter and defaultdict, and know when to reach for the library instead of rolling your own.

3. What survived from Session 21 - Modules, Packages & pip?

Warm-up

Discussion prompt

Before we open Session 22 - Standard Library Tour: without looking back, what was the main idea of Session 21 - Modules, Packages & pip, and what could you do by the end of it that you could not do before?

Hint: One sentence for the idea, one for the skill. If the second one is blank, that is the part to revisit.

Answer:

Session 21 of the Python Fundamentals series, in depth. Reusing code beyond a single file: importing from the standard library (import math, from math import sqrt, import x as np, from x import *), writing and importing your own module, the if __name__ == '__main__' guard, packages as folders of modules, installing third-party code with pip and requirements.txt, isolating projects with virtual environments, and the import search path.

4. Batteries Included

Section

Part 1

5. A library comes with Python

Concept

The standard library is a large set of ready-made modules installed alongside Python itself. No download, no pip - it is already on your machine.

standard library — The collection of modules bundled with Python. 'Batteries included' - common jobs like math, dates, files, and randomness already have tested tools.

6. Break it if you can: A library comes with Python

Counterexample

Discussion prompt

The standard library is a large set of ready-made modules installed alongside Python itself. No download, no pip - it is already on your machine.

That is stated as though it always holds. Do one of two things: produce a case where it fails, or say precisely what rules such a case out. "It just does" is not on the menu.

Hint: Hunt at the extremes first — zero, one, negative, empty, equal. If every extreme survives, the reason they survive is the proof.

7. import brings a module in

Concept

A module sits idle until you import it. After import math, you reach its tools through the module name: math.sqrt(...).

Put your imports at the top of the file so every line below can use them.

8. By analogy: import brings a module in

Analogy

Discussion prompt

Explain import brings a module in by analogy to something with no Python Fundamentals in it at all — a queue, a recipe, a map, a bank balance, whatever fits. Then say where your analogy breaks.

Hint: An analogy that never breaks is not an analogy, it is the same idea wearing a hat. Find the seam — that is the part that is actually new.

Answer:

A module sits idle until you import it. After import math, you reach its tools through the module name: math.sqrt(...).

9. Two ways to import

Concept

import math brings in the whole module; you then write math.sqrt. from datetime import date pulls one name in so you write date directly.

you writethen you callreads as
import mathmath.sqrt(9)sqrt, from math
from datetime import datedate(2026, 7, 15)date, imported by name

10. Fill in: then you call for Two ways to import

Comparison

Comparison matrix

From Two ways to import: refill the then you call column from what you know. The rest of the table is as it appeared.

you writethen you callreads as
import mathmath.sqrt(9)sqrt, from math
from datetime import datedate(2026, 7, 15)date, imported by name

11. Import and use a module

Worked example

import math

print(math.sqrt(144))
print(math.floor(3.7))

Line 1 loads the math module

Why: Now the name math points at the whole toolbox of math functions.

Call tools through the module name

Why: Verified by execution: math.sqrt(144) is 12.0 and math.floor(3.7) is 3.

callreturns
math.sqrt(144)12.0
math.floor(3.7)3

12. What each one costs: Import and use a module

Trade off

Comparison matrix

From Import and use a module: every row here is a choice with a cost. Fill the returns column, then say which row you would actually pick and what you give up for it.

callreturns
math.sqrt(144)12.0
math.floor(3.7)3

13. Something is wrong here: using a tool before importing it

Anomaly

Predict first

A student writes this, and it looks reasonable:

You call a function without importing its module first.

It is wrong. Say what breaks — and say it before you turn the page.

Correct: sqrt lives inside math, not in the plain language.

Import the module, then reach the tool through it.

Why: sqrt lives inside math, not in the plain language. With no import, the name is undefined and Python raises NameError.

14. Trap: using a tool before importing it

Trap

The trap

You call a function without importing its module first.

sqrt(16)

Python does not know the bare name sqrt

Why: sqrt lives inside math, not in the plain language. With no import, the name is undefined and Python raises NameError.

you writeresult
sqrt(16)NameError: name 'sqrt' is not defined

The fix

Import the module, then reach the tool through it.

import math

print(math.sqrt(16))

math.sqrt is found and runs

Why: Verified by execution: prints 4.0. The name lives on the module, so you say math.sqrt, not sqrt.

you writeprints
math.sqrt(16)4.0

15. Inspect it line by line: Trap: using a tool before importing it

Error analysis

Annotate

Walk the callouts on Trap: using a tool before importing it. Each one is a place this is easy to get subtly wrong.

  • sqrt lives inside math, not in the plain language. With no import, the name is undefined and Python raises NameError.
  • Verified by execution: prints 4.0. The name lives on the module, so you say math.sqrt, not sqrt.

16. math: Numbers Done Right

Section

Part 2

17. math handles the tricky arithmetic

Concept

The math module gives you square roots, rounding to whole numbers, constants like pi, and more - all tested and fast.

floor rounds down to the nearest whole number; ceil rounds up. Both return an int.

18. Teach it back: math handles the tricky arithmetic

Explain it

Discussion prompt

Explain math handles the tricky arithmetic to a student a year behind you. No notation, no jargon they have not met — and it still has to be true.

Hint: If your explanation needs a symbol they have never seen, you are describing the notation rather than the idea.

Answer:

The math module gives you square roots, rounding to whole numbers, constants like pi, and more - all tested and fast.

19. Predict the next row: sqrt, floor, and ceil

Pattern

Predict first

The table runs: math.sqrt(2) | 1.4142135623730951 · math.floor(3.7) | 3

In sqrt, floor, and ceil, given the rows so far: what is the next one — the row where call is math.ceil(3.2)?

Correct: math.ceil(3.2) | 4

callreturns
math.sqrt(2)1.4142135623730951
math.floor(3.7)3
math.ceil(3.2)4

Why: The relationship between the columns, not the individual numbers, is what generates the next row. Verified by execution: math.sqrt(2) is 1.4142135623730951 - a full-precision float.

20. sqrt, floor, and ceil

Worked example

import math

print(math.sqrt(2))
print(math.floor(3.7))
print(math.ceil(3.2))

sqrt gives a float

Why: Verified by execution: math.sqrt(2) is 1.4142135623730951 - a full-precision float.

floor rounds down, ceil rounds up

Why: Verified by execution: floor(3.7) is 3, ceil(3.2) is 4 - never the nearest, always the direction named.

callreturns
math.sqrt(2)1.4142135623730951
math.floor(3.7)3
math.ceil(3.2)4

21. Draw the shape of it: sqrt, floor, and ceil

Blank canvas

Draw it

Draw what sqrt, floor, and ceil just did — the shape of it, not the line-by-line working. One picture, labels only where you need them. Then check it against the steps: anything you could not draw is a step you followed rather than understood.

22. floor and ceil with negatives

Concept

'Down' means toward negative infinity, not toward zero. So floor(-2.1) is -3, and ceil(-2.1) is -2.

This trips people up - down on the number line, not down in size.

23. Rounding a negative

Worked example

import math

print(math.floor(-2.1))
print(math.ceil(-2.1))

floor moves left on the number line

Why: Verified by execution: floor(-2.1) is -3 (the whole number just below -2.1).

ceil moves right

Why: Verified by execution: ceil(-2.1) is -2 (the whole number just above -2.1).

callreturns
math.floor(-2.1)-3
math.ceil(-2.1)-2

24. Use pi for a circle

Worked example

import math

r = 3
print(round(math.pi * r ** 2, 2))
print(round(2 * math.pi * r, 2))

math.pi is the constant 3.141592653589793

Why: You never type pi's digits - the module holds it to full precision.

Area then circumference, rounded

Why: Verified by execution: pirr is 28.27 and 2pir is 18.85 after round(..., 2).

formulavaluerounded
math.pi3.141592653589793-
pi * 3 ** 228.274...28.27
2 * pi * 318.849...18.85

25. Someone already solved this

Intuition

Every function in math was written, tested, and optimized by experts. Typing math.sqrt(2) borrows all of that work for free.

The habit to build: when a job feels common - roots, dates, randomness, counting - assume the library already has it, and go look.

26. random: Controlled Chance

Section

Part 3

27. random makes unpredictable values

Concept

The random module produces pseudo-random results: randint(a, b) for a whole number in a range, choice(seq) to pick an item, shuffle(list) to reorder in place.

randint(a, b) — A random integer from a to b, including both ends. randint(1, 6) is a fair six-sided die.

28. Take the definitions apart: standard library vs randint(a, b)

Definition probe

Sort into buckets

Every line below is part of the definition of standard library or of randint(a, b) — one or the other, never both. Put each where it belongs.

standard library
The collection of modules bundled with Python.; 'Batteries included' - common jobs like math, dates, files, and randomness already have tested tools.
randint(a, b)
A random integer from a to b, including both ends.; randint(1, 6) is a fair six-sided die.
b1
The collection of modules bundled with Python. 'Batteries included' - common jobs like math, dates, files, and randomness already have tested tools.
b2
A random integer from a to b, including both ends. randint(1, 6) is a fair six-sided die.

29. seed makes it reproducible

Concept

random.seed(n) fixes the starting point of the sequence. Same seed, same numbers, every run - essential for tests and for demos you want to repeat.

Every example here calls seed first so the output is identical on your machine.

30. Seeded dice rolls

Worked example

import random

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

seed(42) fixes the sequence

Why: From this seed the rolls are fully determined - anyone running it sees the same three numbers.

Three rolls come out in order

Why: Verified by execution: 6, then 1, then 1.

callroll
first randint(1, 6)6
second randint(1, 6)1
third randint(1, 6)1

31. Where does each piece belong: Session 22 - Standard Library Tour

Sorting

Sort into buckets

These are the pieces of Session 22 - Standard Library Tour, out of order. Put each one back under the part of the lesson it belongs to.

Batteries Included
A library comes with Python; import brings a module in; Two ways to import
math: Numbers Done Right
math handles the tricky arithmetic; sqrt, floor, and ceil; floor and ceil with negatives
random: Controlled Chance
random makes unpredictable values; seed makes it reproducible; Seeded dice rolls
s1
Batteries Included is where Session 22 - Standard Library Tour puts A library comes with Python, import brings a module in, Two ways to import. Knowing which part of the lesson a problem belongs to is most of knowing which method to reach for.
s2
math: Numbers Done Right is where Session 22 - Standard Library Tour puts math handles the tricky arithmetic, sqrt, floor, and ceil, floor and ceil with negatives. Knowing which part of the lesson a problem belongs to is most of knowing which method to reach for.
s3
random: Controlled Chance is where Session 22 - Standard Library Tour puts random makes unpredictable values, seed makes it reproducible, Seeded dice rolls. Knowing which part of the lesson a problem belongs to is most of knowing which method to reach for.

32. Restore the missing line: choice and shuffle

Fill the middle

Fill in the blanks

From choice and shuffle — one line has had its right-hand side removed. Put it back.

import random

random.seed(7)
deck = [1, 2, 3, 4, 5]
random.shuffle(deck)
print(deck)

Why: deck is what everything below it consumes, so the wrong expression here fails later and somewhere else. It changes deck itself and returns None - so you print deck, not the call.

33. choice and shuffle

Worked example

import random

random.seed(7)
deck = [1, 2, 3, 4, 5]
random.shuffle(deck)
print(deck)

shuffle reorders the list in place

Why: It changes deck itself and returns None - so you print deck, not the call.

Read the shuffled order

Why: Verified by execution: [5, 1, 4, 2, 3] for seed 7.

stepdeck
before shuffle[1, 2, 3, 4, 5]
after shuffle[5, 1, 4, 2, 3]

34. choice picks one item

Worked example

import random

random.seed(0)
colors = ["red", "green", "blue"]
print(random.choice(colors))
print(random.choice(colors))

choice returns one element

Why: It reaches into the list and hands back a single item, leaving the list unchanged.

Read both picks

Why: Verified by execution: green, then green - for seed 0 both picks land on the same item.

callreturns
first choice(colors)green
second choice(colors)green

35. Something is wrong here: random without a seed

Anomaly

Predict first

A student writes this, and it looks reasonable:

No seed - so the same code gives different numbers each run, and a bug you saw once may never come back.

It is wrong. Say what breaks — and say it before you turn the page.

Correct: With no fixed starting point, you cannot reproduce a result.

Seed first - same seed, same sequence, every time.

Why: With no fixed starting point, you cannot reproduce a result. There is no single 'correct' output to trace - that is the whole problem.

36. Trap: random without a seed

Trap

The trap

No seed - so the same code gives different numbers each run, and a bug you saw once may never come back.

import random

print(random.randint(1, 100))
# run again -> a different number

Every run diverges

Why: With no fixed starting point, you cannot reproduce a result. There is no single 'correct' output to trace - that is the whole problem.

runoutput
first rununpredictable
second runusually different

The fix

Seed first - same seed, same sequence, every time.

import random

random.seed(1)
print(random.randint(1, 100))
random.seed(1)
print(random.randint(1, 100))

Reset to seed 1 and the value repeats

Why: Verified by execution: 18, then 18. Re-seeding rewinds the sequence to the same spot.

stepprints
seed(1); randint18
seed(1); randint18

37. Break it on purpose: random without a seed

Break the constraint

Discussion prompt

The rule this trap just fixed:

Verified by execution: 18, then 18. Re-seeding rewinds the sequence to the same spot.

Now break it on purpose. Build a case that violates it and follow the consequences until something visibly fails. Where does the failure first show up — and would you have noticed it if you had not been looking?

Hint: The dangerous rules are the ones whose violation still produces an answer. If yours fails loudly, try to find one that fails quietly.

Answer:

With no fixed starting point, you cannot reproduce a result. There is no single 'correct' output to trace - that is the whole problem.

38. datetime: Dates & Days

Section

Part 4

39. date represents a calendar day

Concept

From the datetime module, date(year, month, day) builds one calendar day. It knows its .year, .month, and .day.

date.today() gives the current day from the system clock - great for real programs, but its value changes daily, so the examples here use a fixed date.

40. Build a date and read its parts

Worked example

from datetime import date

d = date(2026, 7, 15)
print(d)
print(d.year, d.month, d.day)

from ... import pulls one name in

Why: from datetime import date lets you write date(...) directly instead of datetime.date(...).

Print the day and its fields

Why: Verified by execution: printing d shows 2026-07-15; the fields are 2026 7 15.

expressionvalue
d2026-07-15
d.year2026
d.month7
d.day15

41. Watch it run: Build a date and read its parts

Pattern

Step through it

Step through Build a date and read its parts one row at a time. What is driving the change, and what would the row after the last one be?

  1. Step 1: expression is d
  2. Step 2: expression is d.year
  3. Step 3: expression is d.month
  4. Step 4: expression is d.day

42. timedelta is a span of time

Concept

timedelta(days=n) is a length of time you can add to or subtract from a date. Date plus span gives a new date.

Subtract one date from another and you get a timedelta back - ask it .days for the gap.

43. Finish it with less help: Add days with timedelta

Faded example

Fill in the blanks

Add days with timedelta, with the scaffolding fading: two lines are gone now — fill both.

from datetime import date, timedelta

d = date(2026, 7, 15)
later = d + timedelta(days=10)
print(later)

Why: Reproducing these unaided, rather than reading them, is what tells you the method has transferred. timedelta(days=10) is the length; adding it rolls the calendar forward, handling month ends for you.

44. Add days with timedelta

Worked example

from datetime import date, timedelta

d = date(2026, 7, 15)
later = d + timedelta(days=10)
print(later)

Add a 10-day span to the date

Why: timedelta(days=10) is the length; adding it rolls the calendar forward, handling month ends for you.

Read the new date

Why: Verified by execution: 2026-07-25.

stepvalue
d2026-07-15
timedelta(days=10)10 days
d + timedelta2026-07-25

45. Fill in: value for Add days with timedelta

Comparison

Comparison matrix

From Add days with timedelta: refill the value column from what you know. The rest of the table is as it appeared.

stepvalue
d2026-07-15
timedelta(days=10)10 days
d + timedelta2026-07-25

46. Finish it with less help: Days between two dates

Faded example

Fill in the blanks

Days between two dates, with the scaffolding fading: two lines are gone now — fill both.

from datetime import date

start = date(2026, 1, 1)
end = date(2026, 7, 15)
gap = end - start
print(gap.days)

Why: Reproducing these unaided, rather than reading them, is what tells you the method has transferred. end - start is a span; printing it whole shows '195 days, 0:00:00'.

47. Days between two dates

Worked example

from datetime import date

start = date(2026, 1, 1)
end = date(2026, 7, 15)
gap = end - start
print(gap.days)

Subtracting dates gives a timedelta

Why: end - start is a span; printing it whole shows '195 days, 0:00:00'.

Ask the span for its days

Why: Verified by execution: gap.days is 195.

expressionvalue
end - start195 days, 0:00:00
gap.days195

48. Format a date with strftime

Worked example

from datetime import date

d = date(2026, 7, 15)
print(d.strftime("%Y-%m-%d"))
print(d.strftime("%B %d, %Y"))
print(d.strftime("%A"))

strftime turns a date into text

Why: Each %-code is a placeholder: %Y year, %m month, %d day, %B month name, %A weekday name.

Read the three formats

Why: Verified by execution: 2026-07-15, then July 15, 2026, then Wednesday.

format stringoutput
%Y-%m-%d2026-07-15
%B %d, %YJuly 15, 2026
%AWednesday

49. Watch it run: Format a date with strftime

Pattern

Step through it

Step through Format a date with strftime one row at a time. What is driving the change, and what would the row after the last one be?

  1. Step 1: format string is %Y-%m-%d
  2. Step 2: format string is %B %d, %Y
  3. Step 3: format string is %A

50. pathlib: Paths as Objects

Section

Part 5

51. Path models a file location

Concept

from pathlib import Path gives you Path, an object that represents a file or folder location and knows how to take itself apart.

Path — A pathlib object standing for a filesystem path. It exposes parts like .name and .suffix and can check .exists() - no manual string slicing.

52. name, suffix, and stem

Worked example

from pathlib import Path

p = Path("reports/summary.txt")
print(p.name)
print(p.suffix)
print(p.stem)

Ask the path for its pieces

Why: .name is the final component, .suffix the extension, .stem the name without the extension.

Read the parts

Why: Verified by execution: summary.txt, then .txt, then summary.

attributevalue
p.namesummary.txt
p.suffix.txt
p.stemsummary

53. Watch it run: name, suffix, and stem

Pattern

Step through it

Step through name, suffix, and stem one row at a time. What is driving the change, and what would the row after the last one be?

  1. Step 1: attribute is p.name
  2. Step 2: attribute is p.suffix
  3. Step 3: attribute is p.stem

54. The / operator joins paths

Concept

Once you have a Path, the / operator joins a folder and a child: Path("data") / "scores.csv". Python inserts the right separator for your operating system.

No more gluing strings with slashes by hand - and no more guessing / versus \.

55. Build a path with /

Worked example

from pathlib import Path

full = Path("data") / "scores.csv"
print(full.name)
print(full.suffix)
print(full.as_posix())

/ joins folder and file into one Path

Why: The result is a Path, so it still knows its own .name and .suffix.

Read the joined path

Why: Verified by execution: name is scores.csv, suffix is .csv, and .as_posix() shows data/scores.csv with forward slashes.

expressionvalue
full.namescores.csv
full.suffix.csv
full.as_posix()data/scores.csv

56. Restore the missing line: Check whether a path exists

Fill the middle

Fill in the blanks

From Check whether a path exists — one line has had its right-hand side removed. Put it back.

from pathlib import Path

p = Path("notes.txt")
print(p.exists())
missing = Path("ghost.txt")
print(missing.exists())

Why: missing is what everything below it consumes, so the wrong expression here fails later and somewhere else. It returns True if a real file or folder is there, False otherwise - a safe check before you read.

57. Check whether a path exists

Worked example

from pathlib import Path

p = Path("notes.txt")
print(p.exists())
missing = Path("ghost.txt")
print(missing.exists())

.exists() asks the filesystem

Why: It returns True if a real file or folder is there, False otherwise - a safe check before you read.

Read both answers

Why: Verified by execution in a folder holding notes.txt but not ghost.txt: True, then False.

pathexists()
notes.txt (present)True
ghost.txt (absent)False

58. A Path knows things a string cannot

Intuition

"data/scores.csv" is just characters. A Path is an object that understands it is a path - so you ask it questions instead of slicing text and hoping.

That is the pattern across the library: wrap raw data in the right object and the useful operations come along for free.

59. collections: Better Containers

Section

Part 6

60. Counter tallies for you

Concept

Counter from collections counts how often each item appears. Hand it a list or string and it returns a dict-like tally.

Ask for a missing key and you get 0, not an error - it assumes anything uncounted has a count of zero.

61. Teach it back: Counter tallies for you

Explain it

Discussion prompt

Explain Counter tallies for you to a student a year behind you. No notation, no jargon they have not met — and it still has to be true.

Hint: If your explanation needs a symbol they have never seen, you are describing the notation rather than the idea.

Answer:

Counter from collections counts how often each item appears. Hand it a list or string and it returns a dict-like tally.

62. Count votes with Counter

Worked example

from collections import Counter

votes = ["yes", "no", "yes", "yes", "no"]
tally = Counter(votes)
print(tally)
print(tally["yes"])
print(tally["maybe"])

Counter walks the list and tallies

Why: One pass builds the counts; printing it shows Counter({'yes': 3, 'no': 2}).

Look up present and absent keys

Why: Verified by execution: tally['yes'] is 3, and a never-seen key like 'maybe' is 0 (no KeyError).

expressionvalue
tallyCounter({'yes': 3, 'no': 2})
tally['yes']3
tally['maybe']0

63. most_common finds the top item

Worked example

from collections import Counter

words = "the cat the dog the bird".split()
c = Counter(words)
print(c.most_common(2))

split makes a list of words

Why: "the cat the dog the bird".split() is ['the', 'cat', 'the', 'dog', 'the', 'bird'].

most_common(2) ranks the top two

Why: Verified by execution: [('the', 3), ('cat', 1)] - most frequent first, as (item, count) pairs.

wordcount
the3
cat1
dog1
bird1

64. Which is which, by count

Discrimination

Sort into buckets

Sort these by count, from memory, without looking back at most_common finds the top item. Telling them apart on the spot is the skill; the table is only where the answer happens to be written down.

3
the
1
cat; dog; bird
g1
count is "3" for the — that is what the table on "most_common finds the top item" records, and it is the single property separating this group from the rest.
g2
count is "1" for cat, dog, bird — that is what the table on "most_common finds the top item" records, and it is the single property separating this group from the rest.

65. defaultdict supplies a starting value

Concept

A defaultdict(int) starts any missing key at 0; a defaultdict(list) starts it at an empty list. You skip the 'is the key there yet?' check.

defaultdict(factory) — A dict that auto-creates a missing key using factory() - int gives 0, list gives []. The factory is the type you pass in.

66. Term to definition: Session 22 - Standard Library Tour

Matching

Match the pairs

Match each term to the definition this lesson gave it — not the one you would guess from the word.

  • t1. standard library
  • t2. randint(a, b)
  • t3. Path
  • t4. defaultdict(factory)
  • d1. The collection of modules bundled with Python. 'Batteries included' - common jobs like math, dates, files, and randomness already have tested tools.
  • d2. A random integer from a to b, including both ends. randint(1, 6) is a fair six-sided die.
  • d3. A pathlib object standing for a filesystem path. It exposes parts like .name and .suffix and can check .exists() - no manual string slicing.
  • d4. A dict that auto-creates a missing key using factory() - int gives 0, list gives []. The factory is the type you pass in.

Why: These are the working definitions of standard library, randint(a, b), Path, defaultdict(factory) as Session 22 - Standard Library Tour uses them. Pairing them correctly is the test of whether you could state each one with the slide switched off.

67. Group items with defaultdict(list)

Worked example

from collections import defaultdict

groups = defaultdict(list)
groups["fruit"].append("apple")
groups["fruit"].append("pear")
groups["veg"].append("kale")
print(groups["fruit"])

First touch of a key makes an empty list

Why: groups['fruit'] did not exist, so defaultdict created [] - then .append works immediately.

Items collect under each key

Why: Verified by execution: groups['fruit'] is ['apple', 'pear'].

stepgroups['fruit']
append 'apple'['apple']
append 'pear'['apple', 'pear']

68. Draw the shape of it: Group items with defaultdict(list)

Blank canvas

Draw it

Draw what Group items with defaultdict(list) just did — the shape of it, not the line-by-line working. One picture, labels only where you need them. Then check it against the steps: anything you could not draw is a step you followed rather than understood.

69. Restore the missing line: Count letters with defaultdict(int)

Fill the middle

Fill in the blanks

From Count letters with defaultdict(int) — one line has had its right-hand side removed. Put it back.

from collections import defaultdict

counts = defaultdict(int)
for ch in "hello":
counts[ch] += 1
print(dict(counts))

Why: counts is what everything below it consumes, so the wrong expression here fails later and somewhere else. counts[ch] += 1 works even the first time: the missing key defaults to 0 before adding.

70. Count letters with defaultdict(int)

Worked example

from collections import defaultdict

counts = defaultdict(int)
for ch in "hello":
    counts[ch] += 1
print(dict(counts))

Each new letter starts at 0, then +1

Why: counts[ch] += 1 works even the first time: the missing key defaults to 0 before adding.

Trace the counts through the word

Why: Verified by execution: final dict is {'h': 1, 'e': 1, 'l': 2, 'o': 1}.

charcounts afternote
h{h: 1}new, 0 then +1
e{h: 1, e: 1}new
l{h: 1, e: 1, l: 1}new
l{h: 1, e: 1, l: 2}seen again
o{h: 1, e: 1, l: 2, o: 1}new

71. Where the cost goes: Count letters with defaultdict(int)

Cost model

Annotate

In Count letters with defaultdict(int), before reading the notes: mark where the time actually goes. Which line dominates?

  • counts[ch] += 1 works even the first time: the missing key defaults to 0 before adding.
  • Verified by execution: final dict is {'h': 1, 'e': 1, 'l': 2, 'o': 1}.

72. Something is wrong here: a plain dict raises KeyError

Anomaly

Predict first

A student writes this, and it looks reasonable:

You read a key that a normal dict has never seen.

It is wrong. Say what breaks — and say it before you turn the page.

Correct: Ordinary dicts have no default, so scores['ben'] raises KeyError: 'ben' and the program stops.

A defaultdict(int) fills in 0 instead of crashing.

Why: Ordinary dicts have no default, so scores['ben'] raises KeyError: 'ben' and the program stops.

73. Trap: a plain dict raises KeyError

Trap

The trap

You read a key that a normal dict has never seen.

scores = {"ana": 90}
print(scores["ben"])

A missing key on a plain dict crashes

Why: Ordinary dicts have no default, so scores['ben'] raises KeyError: 'ben' and the program stops.

you writeresult
scores['ben']KeyError: 'ben'

The fix

A defaultdict(int) fills in 0 instead of crashing.

from collections import defaultdict

d = defaultdict(int)
print(d["new"])

The missing key defaults to 0

Why: Verified by execution: prints 0, and the key 'new' is now stored as 0. Pick the container that matches how you read it.

you writeprints
d['new']0

74. Inspect it line by line: Trap: a plain dict raises KeyError

Error analysis

Annotate

Walk the callouts on Trap: a plain dict raises KeyError. Each one is a place this is easy to get subtly wrong.

  • Ordinary dicts have no default, so scores['ben'] raises KeyError: 'ben' and the program stops.
  • Verified by execution: prints 0, and the key 'new' is now stored as 0. Pick the container that matches how you read it.

75. os & sys: Talking to the System

Section

Part 7

76. os and sys reach outside your code

Concept

os talks to the operating system - the current folder, environment, and file operations. sys describes the running Python itself and its command-line arguments.

You will meet these in depth later; for now, know they exist and where to look.

77. By analogy: os and sys reach outside your code

Analogy

Discussion prompt

Explain os and sys reach outside your code by analogy to something with no Python Fundamentals in it at all — a queue, a recipe, a map, a bank balance, whatever fits. Then say where your analogy breaks.

Hint: An analogy that never breaks is not an analogy, it is the same idea wearing a hat. Find the seam — that is the part that is actually new.

Answer:

os talks to the operating system - the current folder, environment, and file operations. sys describes the running Python itself and its command-line arguments.

78. Predict the next row: Peek at the environment

Pattern

Predict first

The table runs: sys.version_info.major | 3 · sys.version_info.minor | 12

In Peek at the environment, given the rows so far: what is the next one — the row where expression is sys.platform?

Correct: sys.platform | win32

expressionvalue (this run)
sys.version_info.major3
sys.version_info.minor12
sys.platformwin32

Why: The relationship between the columns, not the individual numbers, is what generates the next row. Its .major and .minor fields are the version numbers - useful when code needs a certain Python.

79. Peek at the environment

Worked example

import sys

print(sys.version_info.major, sys.version_info.minor)
print(sys.platform)

sys.version_info reports the Python version

Why: Its .major and .minor fields are the version numbers - useful when code needs a certain Python.

sys.platform names the OS family

Why: Verified by execution on this machine: 3 12, then win32 (Windows). On other systems it reads linux or darwin.

expressionvalue (this run)
sys.version_info.major3
sys.version_info.minor12
sys.platformwin32

80. What each one costs: Peek at the environment

Trade off

Comparison matrix

From Peek at the environment: every row here is a choice with a cost. Fill the value (this run) column, then say which row you would actually pick and what you give up for it.

expressionvalue (this run)
sys.version_info.major3
sys.version_info.minor12
sys.platformwin32

81. Don't reinvent what ships in the box

Concept

Before you hand-write square roots, date math, counting loops, or path parsing, check the library. The built-in version is tested, faster, and handles edge cases you would miss.

Writing your own is a great way to learn, but for real programs, reach for the tool that already works.

82. Break it if you can: Don't reinvent what ships in the box

Counterexample

Discussion prompt

Before you hand-write square roots, date math, counting loops, or path parsing, check the library. The built-in version is tested, faster, and handles edge cases you would miss.

That is stated as though it always holds. Do one of two things: produce a case where it fails, or say precisely what rules such a case out. "It just does" is not on the menu.

Hint: Hunt at the extremes first — zero, one, negative, empty, equal. If every extreme survives, the reason they survive is the proof.

Answer:

Writing your own is a great way to learn, but for real programs, reach for the tool that already works.

83. Something is wrong here: reinventing sqrt

Anomaly

Predict first

A student writes this, and it looks reasonable:

Guessing a square root by hand - slow, and only right for perfect squares you happen to know.

It is wrong. Say what breaks — and say it before you turn the page.

Correct: This prints 144 - it confirms 12 squared, but it never actually finds the root of a number you do not already know.

Ask the library for the root directly.

Why: This prints 144 - it confirms 12 squared, but it never actually finds the root of a number you do not already know.

84. Trap: reinventing sqrt

Trap

The trap

Guessing a square root by hand - slow, and only right for perfect squares you happen to know.

guess = 12
print(guess * guess)

You are checking, not computing

Why: This prints 144 - it confirms 12 squared, but it never actually finds the root of a number you do not already know.

you writeprints
guess * guess144

The fix

Ask the library for the root directly.

import math

print(math.sqrt(144))

math.sqrt computes it for any number

Why: Verified by execution: 12.0 - and it works for 2, 7, or 1000 just the same, no guessing.

you writeprints
math.sqrt(144)12.0

85. Which of these survive contact with Session 22 - Standard Library Tour?

Two truths and a lie

Sort into buckets

Some of these hold up and some are the exact mistakes this lesson is built to prevent. Sort them.

Holds up
The standard library is a large set of ready-made modules installed alongside Python itself. No download, no pip - it is already on your machine.; A module sits idle until you import it. After import math, you reach its tools through the module name: math.sqrt(...).; import math brings in the whole module; you then write math.sqrt. from datetime import date pulls one name in so you write date directly.
Breaks
You call a function without importing its module first.; No seed - so the same code gives different numbers each run, and a bug you saw once may never come back.
sound
These are stated as this lesson states them — each one survives the edge cases Session 22 - Standard Library Tour puts it through.
flawed
Each of these is lifted from a trap in this deck: reasonable-sounding, and wrong in a way that only shows up once you rely on it.

86. Patterns & Checks

Section

Part 8

87. Using any standard-library module

Pattern

1. import the module at the top of the file

Why: import math, import random, from datetime import date - do it once, up top.

2. Reach tools through the module name

Why: math.sqrt(...), random.randint(...) - the dot says 'this tool lives in that module'.

3. Check the docs for what it already does

Why: docs.python.org lists every function; the answer to 'is there a built-in for this?' is usually yes.

4. Seed anything random you want to reproduce

Why: random.seed(n) before the draws makes the run repeatable for tests and demos.

88. Reach for the library first

Pattern

Roots, rounding, pi -> math

Why: math.sqrt, math.floor, math.ceil, math.pi cover the common arithmetic.

Dice, picks, shuffles -> random (with a seed)

Why: randint, choice, shuffle - seed first when you need the same result twice.

Days and calendars -> datetime

Why: date, timedelta, strftime handle date math and formatting so you never count days by hand.

Files, paths, tallies -> pathlib and collections

Why: Path for locations, Counter and defaultdict for counting and grouping.

89. Where this shows up: Session 22 - Standard Library Tour

Real world

Discussion prompt

Outside this lesson: where does Session 22 - Standard Library Tour actually turn up? Name one concrete situation — a job, a piece of software someone ships, a decision somebody has to make — and say which part of Reach for the library first is doing the work in it.

Hint: Vague is the failure mode here. "Engineering" is not a situation; "deciding whether this build is fast enough to ship" is.

Answer:

Session 22 of the Python Fundamentals series: the 'batteries included' tour. A guided, runnable walk through the modules you reach for daily - math (sqrt, floor, ceil, pi), random (randint, choice, shuffle, and seed for reproducibility), datetime (date, timedelta, strftime), pathlib.Path (the / operator, name/suffix/stem, exists), and collections (Counter, defaultdict) - plus a look at os and sys.

90. Check: floor

Check

Round it the floor way.

import math

print(math.floor(4.9))
callreturns
math.floor(4.9)?

Check your understanding

What does this print?

  • A. 4 (correct)
  • B. 5
  • C. 4.9
  • D. 4.0

Answer: A

Why: floor always rounds down to the nearest whole number, so math.floor(4.9) is 4 (an int). Verified by execution.

Why B tempts people
That is rounding to nearest, or ceil. floor never rounds up, even from 4.9.
Why C tempts people
floor returns a whole number, not the original decimal.
Why D tempts people
floor returns an int (4), not a float (4.0).

91. Check: seed reproducibility

Check

The seed is set twice to the same value.

import random

random.seed(1)
print(random.randint(1, 100))
random.seed(1)
print(random.randint(1, 100))
stepprints
seed(1); randint18
seed(1); randint?

Check your understanding

What is the second line of output?

  • A. 18 (correct)
  • B. A different random number
  • C. 1
  • D. None

Answer: A

Why: Re-seeding with the same value 1 rewinds the sequence, so the next randint is identical: 18 both times. Verified by execution.

Why B tempts people
It would differ only if the seed were not reset. The same seed guarantees the same number.
Why C tempts people
1 is the seed value, not the number drawn. randint(1, 100) returns 18 here.
Why D tempts people
randint returns an integer, not None.

92. Fill in: prints for Check: seed reproducibility

Comparison

Comparison matrix

From Check: seed reproducibility: refill the prints column from what you know. The rest of the table is as it appeared.

stepprints
seed(1); randint18
seed(1); randint?

93. Check: Counter lookup

Check

Count the s's.

from collections import Counter

c = Counter("mississippi")
print(c["s"])
wordlettercount
mississippis?

Check your understanding

What does this print?

  • A. 4 (correct)
  • B. 2
  • C. 11
  • D. KeyError

Answer: A

Why: Counter tallies each character; 'mississippi' has four s's, so c['s'] is 4. Verified by execution.

Why B tempts people
There are four s's in mississippi, not two - count them: mi-ss-i-ss-ippi.
Why C tempts people
11 is the length of the whole word, not the count of one letter.
Why D tempts people
Counter returns 0 for missing keys and a real count for present ones - never a KeyError.

94. Check: timedelta subtraction

Check

Step back one day across a month boundary.

from datetime import date, timedelta

d = date(2026, 3, 1)
print(d - timedelta(days=1))
expressionvalue
d - timedelta(days=1)?

Check your understanding

What does this print?

  • A. 2026-02-28 (correct)
  • B. 2026-03-00
  • C. 2026-02-29
  • D. 2026-02-31

Answer: A

Why: One day before March 1, 2026 is the last day of February. 2026 is not a leap year, so February ends on the 28th. Verified by execution.

Why B tempts people
There is no day 0 - datetime rolls back to the previous real calendar day.
Why C tempts people
Feb 29 exists only in leap years; 2026 is not a leap year.
Why D tempts people
February never has 31 days; datetime knows each month's real length.

95. Check: path suffix

Check

What is the extension?

from pathlib import Path

print(Path("archive.tar.gz").suffix)
pathsuffix
archive.tar.gz?

Check your understanding

What does .suffix return here?

  • A. .gz (correct)
  • B. .tar.gz
  • C. .tar
  • D. gz

Answer: A

Why: .suffix is only the last extension, so it is '.gz'. To get both, .suffixes returns ['.tar', '.gz']. Verified by execution.

Why B tempts people
.suffix returns just the final piece; the full list of both is .suffixes, not .suffix.
Why C tempts people
.tar is the first extension, but .suffix reports the last one.
Why D tempts people
The suffix keeps its leading dot: '.gz', not 'gz'.

96. Check: defaultdict default

Check

Read a key that was never set.

from collections import defaultdict

d = defaultdict(int)
print(d["new"])
factoryd['new']
int?

Check your understanding

What does this print?

  • A. 0 (correct)
  • B. KeyError
  • C. None
  • D. int

Answer: A

Why: defaultdict(int) creates a missing key using int(), which is 0, so d['new'] is 0 and no error is raised. Verified by execution.

Why B tempts people
A plain dict would raise KeyError, but defaultdict supplies a default instead.
Why C tempts people
The factory is int, which produces 0, not None. A defaultdict(lambda: None) would give None.
Why D tempts people
The factory int is called - int() - producing the value 0, not the type name.

97. Check: forgot the import

Check

No import line - what happens?

print(sqrt(16))
has import?result
no?

Check your understanding

What does this produce?

  • A. NameError: name 'sqrt' is not defined (correct)
  • B. 4.0
  • C. 4
  • D. AttributeError

Answer: A

Why: sqrt lives in the math module. Without import math and the math. prefix, the bare name is undefined, raising NameError: name 'sqrt' is not defined. Verified by execution.

Why B tempts people
It would print 4.0 only after import math and calling math.sqrt(16).
Why C tempts people
sqrt returns a float (4.0) once imported, but here it never runs at all.
Why D tempts people
AttributeError is for a missing name on an object (like math.square); an undefined bare name is a NameError.

98. Connect it up: Session 22 - Standard Library Tour

Connect it up

Draw it

One page, no notation unless you need it: draw how these connect — Batteries Included · math: Numbers Done Right · random: Controlled Chance · datetime: Dates & Days · pathlib: Paths as Objects · collections: Better Containers. Put an arrow wherever one of them is what makes another possible, and label the arrow with why.

99. What you can do now

Recap

The standard library ships with Python. import a module, then reach its tools through the module name - and reach for it before writing your own.

Reach forWhen you need
math.sqrt / floor / ceil / piroots, rounding, and the constant pi
random + seed(n)dice, picks, shuffles - reproducibly
date / timedelta / strftimecalendar days, gaps, and formatting
Pathpath pieces, the / join, and .exists()
Counter / defaultdictfast tallying and auto-defaulting keys

The habit that matters most: assume the job is already solved, and go look. Next session we package your own reusable code into a module others can import.

Sources

  1. The Python Standard Library
  2. collections - Container datatypes
  3. pathlib - Object-oriented filesystem paths
  4. All snippets and error messages executed and copied from CPython 3.12. — Author verification run, 2026-07-15 (Python Fundamentals series, Session 22).

Want this taught 1-on-1? Alexander tutors Python Fundamentals — $55/session, free consultation.

Book on Wyzant · Text (657) 465-8108