Standard Library Tour II
The tools that professional programs rely on: output formatting, templates, binary data, threads, logging, weak references, specialised containers, exact decimal arithmetic, and enums.
You will learn
reprlib,pprint,textwrapandlocalefor readable output.string.Templatefor user-editable templates, andstructfor binary records.threadingbasics and theloggingmodule.weakref,array,bisectandheapq.decimal.Decimalfor money, andenum.Enumfor named constants.
Output formatting
reprlib.repr shortens huge or deeply nested containers, which is useful in log messages:
pprint ("pretty print") breaks nested data across lines with indentation:
textwrap wraps paragraphs to a given width:
locale formats numbers the way a particular culture expects, for example with a comma as the decimal separator. Which locales are available depends on the operating system:
Templating
string.Template uses simple $name placeholders. It is meant for templates that non-programmers edit, like e-mail texts or file-naming rules, where the full power of f-strings would be confusing or unsafe.
A subclass can change the placeholder character. Here is a photo-renaming rule where % marks the fields:
| Tool | Use it for |
|---|---|
| f-string | formatting in your own code (the everyday choice) |
str.format | templates stored in your code or config, with format specs |
string.Template | templates written by users, with simple $name fields |
| t-string (3.14) | passing structured templates to libraries for safe processing |
Working with binary data record layouts
Binary files and network protocols store numbers as raw bytes, not as text. struct converts between Python values and packed bytes, according to a format string: < means little-endian (lowest byte first), H is a 2-byte unsigned int, I a 4-byte unsigned int, and f a 4-byte float.
Multi-threading
Threads let several tasks run at the same time. That is useful when a program spends time waiting: for the network, the disk or the user. Start a Thread with a target function, and join() it to wait for it to finish.
โ ๏ธ Pitfalls: threads share memory, so protect shared data with a Lock, or better, pass data through a queue.Queue. In the standard ("GIL") build of CPython, only one thread runs Python code at a time, so threads speed up waiting, not calculating. For CPU-heavy work, use multiprocessing or concurrent.futures.ProcessPoolExecutor. Python 3.13+ also offers an optional free-threaded build without the GIL.
๐ก Tip: concurrent.futures.ThreadPoolExecutor is the easy, high-level way to do this: list(pool.map(fetch, urls)).
Logging
print() is fine for quick checks. Real programs use logging, which has severity levels, timestamps, and destinations (the console, files, servers) that you can switch without touching the code that logs.
| Level | Use for |
|---|---|
DEBUG | details for developers |
INFO | normal events ("saved 3 expenses") |
WARNING (the default threshold) | something unexpected that isn't fatal |
ERROR / CRITICAL | an operation failed / the program can't continue |
By default only WARNING and above are shown, on stderr. Configure it once at the start of the program, and give each module its own named logger:
Weak references
Python frees an object's memory when no references to it are left (this is reference counting, plus a garbage collector for reference cycles). A weak reference points to an object without keeping it alive. That is useful for caches, which shouldn't stop unused objects from being freed.
(A REPL detail: echoing d['primary'] stored the object in _, which is a strong reference. The next echoed value, True, replaced it, so nothing kept the object alive any more.)
Tools for working with lists
array: compact lists of numbers
An array stores numbers of one type as compact raw values. A list of a million numbers holds a million full Python objects; an array holds just the raw bytes.
bisect: keeping a list sorted
heapq: always get the smallest item first
A heap is a list arranged so that the smallest item is always at index 0. Pushing and popping are fast (O(log n)). It is the standard way to build a priority queue.
Together with collections.deque (Chapter 6), these cover most "special list" needs.
Decimal floating-point arithmetic
decimal.Decimal does arithmetic in base 10, exactly like you do on paper. Use it for money, and for any calculation where the results must match human or legal rounding rules.
โ Don't build Decimals from floats
โ Build them from strings
The precision is configurable (the default is 28 significant digits):
fractions.Fraction gives exact rational numbers: Fraction(1, 3) + Fraction(1, 6) == Fraction(1, 2).
Enums: named constants
An enum is a fixed set of named values. It replaces magic strings like "food" that can be mistyped, and its members are dotted names, so they work as value patterns in match (Chapter 4).
More examples
1. Money that always adds up
2. A tiny priority to-do list with heapq
3. Parallel "downloads" with a thread pool
4. A file-name template that users can edit
5. Logging to a file and to the console
Recap
reprlib,pprintandtextwrapmake output readable.string.Templateis for user-edited$nametemplates.struct.pack/unpackconvert values to and from raw bytes using a format string.- Threads help with waiting: use
join(), aLockorQueue, or aThreadPoolExecutor.logginghas levels, handlers and named loggers. weakrefdoesn't keep objects alive.arrayis compact,bisectkeeps a list sorted, andheapqgives a priority queue.- Use
Decimal("โฆ")(from strings!) for money.Enum/StrEnumname your constants.
Exercises
Exercise 1 ๐ข easy predict the output
Which is True? 0.1 + 0.2 == 0.3 or Decimal("0.1") + Decimal("0.2") == Decimal("0.3")?
Show solution
Only the Decimal one. Decimal stores base-10 digits exactly, while binary floats can't represent 0.1 exactly.
Exercise 2 ๐ข easy build it
Use heapq.nsmallest to get the 3 cheapest prices from [9.5, 3.2, 12.0, 1.1, 7.7].
Show solution
For "top N", nsmallest/nlargest are clearer than sorting the whole list and slicing it.
Exercise 3 ๐ข easy fix the bug
The log message never appears. Why? Fix it.
Show solution
The default threshold is WARNING, so INFO messages are dropped until you lower the level.
Exercise 4 ๐ข easy explain why
Why does Decimal(0.1) show a long string of digits, while Decimal("0.1") doesn't?
Show solution
The float 0.1 is already an inexact binary approximation before Decimal sees it, and Decimal faithfully converts that approximation, all of its digits. A string carries the exact decimal digits you meant.
Exercise 5 ๐ก medium build it
Use bisect to turn exam scores into grades: below 60 is F, 60โ69 is D, 70โ79 is C, 80โ89 is B, and 90 or more is A. Grade [33, 99, 77, 70, 89, 90, 100].
Show solution
bisect returns how many breakpoints are โค the score, which is exactly the index of the grade. (The default list is never modified here, so the mutable default is harmless.)
Exercise 6 ๐ก medium predict the output
What does Template("Hi $name, you owe $$${amount}").substitute(name="Ben", amount=5) return?
Show solution
'Hi Ben, you owe $5'. $$ is a literal dollar sign, and ${amount} is the placeholder.
Exercise 7 ๐ก medium build it ยท playground
Create playground/categories.py with a StrEnum called Category (FOOD, RENT, FUN, TRAVEL, MISC) and a function parse_category(text) that returns Category.MISC for unknown values instead of raising.
Show solution
Category(value) looks up a member and raises ValueError for unknown values (EAFP again). A StrEnum member prints as its value.
Exercise 8 ๐ก medium refactor it
Replace these print calls with a named logger that writes INFO and above, using the format LEVEL name: message.
Show solution
Now the level, the format and the destination can all be changed in one place, without editing every message.
Exercise 9 ๐ด hard explain why
A teammate uses 8 threads to speed up a pure-Python prime-number search. It gets no faster. Why, and what should they use instead?
Show solution
In the standard CPython build, the Global Interpreter Lock (GIL) lets only one thread execute Python bytecode at a time, so CPU-bound threads take turns instead of running in parallel. Use multiprocessing or ProcessPoolExecutor (separate processes, each with its own interpreter), or the free-threaded build. Threads are still great for I/O-bound work, because waiting releases the GIL.
Exercise 10 ๐ด hard mini-project ยท playground
Create playground/money_safe.py: a function split_exact(total: str, people: int) that uses Decimal to split a bill into cent-exact shares that add up to the total. Extra cents go to the first people. Log each share at DEBUG level, and include a doctest.
Show solution
It combines Decimal (exact cents), quantize with an explicit rounding mode, lazy %-style logging arguments, and a doctest that documents the behaviour.