Chapter 14 ยท Standard Library & Ecosystem

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, textwrap and locale for readable output.
  • string.Template for user-editable templates, and struct for binary records.
  • threading basics and the logging module.
  • weakref, array, bisect and heapq.
  • decimal.Decimal for money, and enum.Enum for 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:

ToolUse it for
f-stringformatting in your own code (the everyday choice)
str.formattemplates stored in your code or config, with format specs
string.Templatetemplates 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.

LevelUse for
DEBUGdetails for developers
INFOnormal events ("saved 3 expenses")
WARNING (the default threshold)something unexpected that isn't fatal
ERROR / CRITICALan 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, pprint and textwrap make output readable. string.Template is for user-edited $name templates.
  • struct.pack/unpack convert values to and from raw bytes using a format string.
  • Threads help with waiting: use join(), a Lock or Queue, or a ThreadPoolExecutor. logging has levels, handlers and named loggers.
  • weakref doesn't keep objects alive. array is compact, bisect keeps a list sorted, and heapq gives a priority queue.
  • Use Decimal("โ€ฆ") (from strings!) for money. Enum/StrEnum name 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.

Official docs