Python Standard Library Power Tools Cheatsheet

A visual guide to the batteries-included modules you use every day: itertools, collections, functools, datetime and zoneinfo, re, contextlib, and dataclasses. No pip install required.

python
stdlib
cheatsheet
Author

James Balamuta

Published

August 9, 2026

The Python standard library ships a set of power tools you reach for every day without installing anything: itertools to iterate, collections to tally and group, functools to cache and adapt functions, datetime plus zoneinfo for time, re for text, contextlib for resources, and dataclasses for typed records. The recurring mental model in this sheet is one promise: no dependency needed. Everything here comes with CPython itself, so there is nothing to pip install; you only import and go. Where this looks like the heavier sheets in the series, the contrast is the point: this is the “you already have this installed” companion that you reach for before pulling in a dependency, and it deliberately leaves heavy numeric work to the numpy and pandas sheets and HTTP to the requests sheet. Every command assumes the canonical imports (import itertools, from collections import Counter, from functools import lru_cache, import datetime as dt, from zoneinfo import ZoneInfo, import re, from contextlib import contextmanager, from dataclasses import dataclass), and every example was verified live on CPython 3.14.

Complete Python standard library power tools cheatsheet (light mode): eight panels covering itertools for lazy iteration, collections for tallying and grouping, functools for caching and adapting functions, datetime for instants and durations, zoneinfo for IANA time zones, re for regular expressions, contextlib for resource management, and dataclasses for typed records.

Complete Python standard library power tools cheatsheet (dark mode): eight panels covering itertools for lazy iteration, collections for tallying and grouping, functools for caching and adapting functions, datetime for instants and durations, zoneinfo for IANA time zones, re for regular expressions, contextlib for resource management, and dataclasses for typed records.

Download the full cheatsheet

All eight panels in a single, printable SVG.

Light SVG Dark SVG

itertools: Iterate Without Building Lists

itertools gives you composable, lazy iterator building blocks: they stream items on demand and never build a list until you ask, so you can chain, slice, group, and combine huge or infinite sequences in constant memory. The everyday four are chain (concatenate), islice (slice without indexing), product (replace nested loops), and groupby (run-length group adjacent equal keys, which means you must sort by the same key first); accumulate does running totals and pairwise gives a sliding two-window.

itertools panel: concatenate iterables with chain, group adjacent equal keys with groupby after sorting, slice an iterator with islice, cartesian product, running totals with accumulate, consecutive pairs with pairwise.

Lazy building blocks that stream items instead of materializing lists.

itertools panel: concatenate iterables with chain, group adjacent equal keys with groupby after sorting, slice an iterator with islice, cartesian product, running totals with accumulate, consecutive pairs with pairwise.

Lazy building blocks that stream items instead of materializing lists.
import itertools, operator

list(itertools.chain([1, 2], [3, 4]))                  # [1, 2, 3, 4]  (flatten one level)
[(k, list(g)) for k, g in itertools.groupby(rows, key=lambda r: r[0])]  # sort by key first!
list(itertools.islice(range(100), 2, 10, 2))           # [2, 4, 6, 8]  (slice an iterator)
list(itertools.product([1, 2], ["a", "b"]))            # nested loops -> all pairs
list(itertools.accumulate([1, 2, 3, 4]))               # [1, 3, 6, 10]  running totals
list(itertools.pairwise([1, 2, 3, 4]))                 # [(1,2),(2,3),(3,4)]  (3.10+)

See the itertools documentation. groupby only groups adjacent equal keys, so sort by the same key first.

collections: Tally, Group, Queue, Record

When a bare dict or list would need boilerplate, collections has a purpose-built container: Counter tallies occurrences as a multiset (with +, -, and .most_common), defaultdict auto-creates a missing key’s value so you skip the “if key not in d” dance, deque gives O(1) appends and pops at both ends (with an optional maxlen ring buffer), and namedtuple is a tiny immutable record with named fields. ChainMap layers several dicts into one fall-through view, which is perfect for config defaults plus overrides.

collections panel: count occurrences with Counter, auto-seed missing keys with defaultdict, fast both-ends queue with deque and maxlen, rotate a ring buffer, lightweight typed record with namedtuple, layer dicts with ChainMap.

Specialized containers that replace boilerplate dict and list code.

collections panel: count occurrences with Counter, auto-seed missing keys with defaultdict, fast both-ends queue with deque and maxlen, rotate a ring buffer, lightweight typed record with namedtuple, layer dicts with ChainMap.

Specialized containers that replace boilerplate dict and list code.
from collections import Counter, defaultdict, deque, namedtuple, ChainMap

Counter("mississippi").most_common(2)            # [('i', 4), ('s', 4)]  (a multiset)
dd = defaultdict(list); dd["x"].append(1)        # {'x': [1]}  no KeyError
dq = deque([1, 2, 3], maxlen=3); dq.appendleft(0)  # deque([0, 1, 2])  right item falls off
dq.rotate(1)                                     # shift the ring one slot right
Point = namedtuple("Point", ["x", "y"]); p = Point(1, 2)  # p.x, p.y, p._asdict()
dict(ChainMap(overrides, defaults))              # layered lookups, top to bottom

See the collections documentation. Counter supports +, -, &, and |; deque ends are O(1).

functools: Cache, Adapt, and Compose Functions

functools adapts and wraps functions: lru_cache (or its @cache shorthand) memoizes a pure function so repeat calls are instant, partial pre-fills some arguments to make a new callable, reduce folds a sequence down to one value, and cached_property computes an attribute once and then returns the stored result. Always put @wraps(fn) on your own decorators so the wrapper keeps the original function’s name and docstring.

functools panel: memoize with lru_cache, unbounded cache shorthand, pre-fill arguments with partial, fold a sequence with reduce, compute an attribute once with cached_property, keep metadata with wraps.

Wrappers that memoize, pre-fill arguments, fold, and preserve metadata.

functools panel: memoize with lru_cache, unbounded cache shorthand, pre-fill arguments with partial, fold a sequence with reduce, compute an attribute once with cached_property, keep metadata with wraps.

Wrappers that memoize, pre-fill arguments, fold, and preserve metadata.
from functools import lru_cache, cache, partial, reduce, cached_property, wraps
import operator

@lru_cache(maxsize=None)                 # memoize: repeat calls hit the cache drawer
def fib(n): return n if n < 2 else fib(n - 1) + fib(n - 2)
# @cache is the 3.9+ shorthand for lru_cache(maxsize=None)
add10 = partial(operator.add, 10); add10(5)      # 15  (pre-filled argument)
reduce(operator.mul, [1, 2, 3, 4], 1)            # 24  (fold to one value)
# @cached_property over def val(self): ...  computes once, then returns the stored value
# @wraps(fn) inside a decorator keeps the wrapped function's __name__ and __doc__

See the functools documentation. fib.cache_info() reports hits and misses.

datetime: Instants, Dates, and Durations

A datetime is either naive (no timezone, ambiguous) or aware (carries a tzinfo); always prefer aware, and get “now” with dt.datetime.now(dt.UTC) rather than the deprecated utcnow(). Use dt.date for calendar dates, dt.timedelta for durations (datetime arithmetic yields a timedelta), fromisoformat/isoformat to round-trip ISO-8601, and strftime/strptime for custom layouts.

datetime panel: current instant UTC-aware with now, today's date, a duration with timedelta, difference between two instants, parse ISO-8601 with fromisoformat, format and parse custom layouts with strftime and strptime.

Always make datetimes timezone-aware; never use the deprecated utcnow().

datetime panel: current instant UTC-aware with now, today's date, a duration with timedelta, difference between two instants, parse ISO-8601 with fromisoformat, format and parse custom layouts with strftime and strptime.

Always make datetimes timezone-aware; never use the deprecated utcnow().
import datetime as dt

dt.datetime.now(dt.UTC)                          # aware "now"  (dt.UTC is 3.11+ alias)
dt.date.today()                                  # 2026-07-01  (date only, no time)
dt.timedelta(days=1, hours=2, minutes=30)        # a duration to add or subtract
later - earlier                                  # subtraction yields a timedelta
dt.datetime.fromisoformat("2026-07-01T09:30:00-05:00")  # parse ISO-8601 -> aware
aware.strftime("%Y-%m-%d %H:%M %Z")              # format out; strptime parses back in

See the datetime documentation. Avoid the deprecated utcnow() and utcfromtimestamp().

zoneinfo: Real IANA Time Zones

Since Python 3.9 the standard library ships zoneinfo, so you no longer need third-party pytz: ZoneInfo("Area/City") loads a real IANA zone (with full daylight-saving rules) that you attach with tzinfo= or convert to with .astimezone(...). The robust pattern is to store and compute in UTC and only convert to a local zone for display; on minimal systems without the OS zone database, pip install tzdata supplies it.

zoneinfo panel: load a named zone with ZoneInfo, make a naive datetime aware with tzinfo, convert to another zone with astimezone, anchor in UTC and display local, DST handled automatically, list installed zones.

Attach and convert IANA zones with the stdlib (3.9+); no third-party pytz.

zoneinfo panel: load a named zone with ZoneInfo, make a naive datetime aware with tzinfo, convert to another zone with astimezone, anchor in UTC and display local, DST handled automatically, list installed zones.

Attach and convert IANA zones with the stdlib (3.9+); no third-party pytz.
import datetime as dt, zoneinfo
from zoneinfo import ZoneInfo

ZoneInfo("America/Chicago")                      # IANA names, not fixed offsets
dt.datetime(2026, 7, 1, 9, 30, tzinfo=ZoneInfo("America/Chicago"))  # naive -> aware
aware.astimezone(ZoneInfo("America/New_York"))   # same instant, different wall time
dt.datetime.now(dt.UTC).astimezone(ZoneInfo("Europe/Paris"))  # store UTC, render local
# ZoneInfo handles DST automatically across spring-forward / fall-back boundaries
len(zoneinfo.available_timezones())              # 598  (pip install tzdata if missing)

See the zoneinfo documentation. Store and compute in UTC; convert to a local zone only for display.

re: Regular Expressions

Regular expressions match patterns in text: re.compile(r"...") builds a reusable pattern (use raw strings so backslashes survive), then .match anchors at the start while .search scans anywhere, and .findall/.finditer collect every hit. Capture substrings with groups (name them (?P<name>...) and read m.group("name") or m.groupdict()), and rewrite text with re.sub, whose replacement can be a literal string or a function called on each match.

re panel: compile a reusable pattern, match at the start versus search anywhere, pull out captured groups with groupdict, find every occurrence with findall, substitute with a literal or a callable, split on a pattern.

Compile once, then match, search, capture groups, and substitute.

re panel: compile a reusable pattern, match at the start versus search anywhere, pull out captured groups with groupdict, find every occurrence with findall, substitute with a literal or a callable, split on a pattern.

Compile once, then match, search, capture groups, and substitute.
import re

pat = re.compile(r"(?P<year>\d{4})-(?P<month>\d{2})-(?P<day>\d{2})")  # compile once, reuse
pat.match(s)                                     # anchored at the start; re.search scans
m.group("year"), m.groupdict()                   # pull out named captured groups
re.findall(r"\d+", "a1 b22 c333")                # ['1', '22', '333']  every occurrence
re.sub(r"\s+", "_", s)                           # substitute literal or a callable
re.split(r"[,;]\s*", "a, b; c,d")                # ['a', 'b', 'c', 'd']  split on a regex

See the re documentation and the Regular Expression HOWTO. Use raw strings so backslashes survive.

contextlib: Clean Resource Management

contextlib makes the with statement easy to build and use: the @contextmanager decorator turns a single-yield generator into a context manager (setup before the yield, teardown after), suppress(Exc) cleanly swallows an expected exception instead of a try/except that does nothing, and redirect_stdout reroutes prints. For a dynamic or unknown number of resources, ExitStack enters them as you go and unwinds them in reverse on exit.

contextlib panel: write a context manager from a generator with contextmanager, swallow an expected exception with suppress, capture printed output with redirect_stdout, manage dynamic resources with ExitStack, guarantee close with closing, reuse a manager as a decorator with ContextDecorator.

Build with-blocks, swallow expected errors, and stack cleanups.

contextlib panel: write a context manager from a generator with contextmanager, swallow an expected exception with suppress, capture printed output with redirect_stdout, manage dynamic resources with ExitStack, guarantee close with closing, reuse a manager as a decorator with ContextDecorator.

Build with-blocks, swallow expected errors, and stack cleanups.
import contextlib
from contextlib import contextmanager, suppress, redirect_stdout, ExitStack, closing

@contextmanager                                  # setup before yield, teardown after
def tag(name): ...; yield ...
with suppress(FileNotFoundError): ...            # swallow an expected exception
with redirect_stdout(buf): print("hi")           # reroute print into a buffer
with ExitStack() as stack: stack.enter_context(open("a"))  # dynamic count, LIFO unwind
with closing(thing) as t: ...                    # guarantee .close() on exit
class T(contextlib.ContextDecorator): ...        # use as a with-block or an @T() decorator

See the contextlib documentation. ExitStack enters resources as you go and unwinds them in reverse.

dataclasses: Typed Records Without Boilerplate

Decorate a class with @dataclass and annotate its fields, and Python generates __init__, __repr__, and __eq__ for you, so the class becomes a clean typed record. Use field(default_factory=list) for mutable defaults (never a bare = [], which is shared across instances), frozen=True for immutability, slots=True to drop __dict__, order=True for sortability, and __post_init__ plus field(init=False) for derived fields; asdict and replace convert and copy.

dataclasses panel: generate a record from annotations with dataclass, mutable default the safe way with default_factory, immutable memory-light record with frozen and slots, sortable instances with order, convert to a dict with asdict and copy with replace, derive a field after init with post_init.

Annotate fields; get init, repr, and eq generated for you.

dataclasses panel: generate a record from annotations with dataclass, mutable default the safe way with default_factory, immutable memory-light record with frozen and slots, sortable instances with order, convert to a dict with asdict and copy with replace, derive a field after init with post_init.

Annotate fields; get init, repr, and eq generated for you.
from dataclasses import dataclass, field, asdict, replace, fields

@dataclass                                       # generates __init__, __repr__, __eq__
class Point: x: int; y: int = 0
tags: list[str] = field(default_factory=list)    # "= []" is shared; use a factory
@dataclass(frozen=True, slots=True)              # immutable + no __dict__
class Frozen: x: int                             # p.x = 5 -> FrozenInstanceError
@dataclass(order=True)                           # generates __lt__ and friends
class Ranked: score: int
asdict(p); replace(p, y=9)                       # to a dict; modified copy (original kept)

See the dataclasses documentation. Set a derived field(init=False) field inside __post_init__.

Quick Reference

Pick the stdlib power tool.
When you need to… Reach for Module
Concatenate / slice / group / combine iterables lazily chain, islice, groupby, product itertools
Running totals or a sliding window accumulate, pairwise itertools
Count things Counter collections
A dict that auto-creates missing values defaultdict collections
Fast both-ends queue / ring buffer deque(maxlen=...) collections
A tiny named record (tuple-backed) namedtuple collections
Memoize a pure function @lru_cache / @cache functools
Pre-fill arguments / fold a sequence partial, reduce functools
Compute an attribute once @cached_property functools
A timezone-aware “now” datetime.now(dt.UTC) datetime
Real IANA time zones, convert between them ZoneInfo, .astimezone zoneinfo
Match / capture / replace in text re.compile, .search, re.sub re
A reusable with-block @contextmanager contextlib
Swallow an expected error suppress(Exc) contextlib
A typed record with __init__/__repr__/__eq__ @dataclass dataclasses
itertools building blocks.
Call Returns (lazy iterator over)
chain(a, b) items of a then b
chain.from_iterable(iters) items of each inner iterable, flattened one level
islice(it, start, stop, step) a sliced window (no negative indices)
groupby(sorted_it, key) (key, group) pairs over adjacent equal keys
product(a, b) every (x, y) pair (nested loops)
accumulate(it[, func]) running reduction (sum by default)
pairwise(it) consecutive overlapping pairs
combinations(it, r) / permutations(it, r) r-length selections / orderings
count(start, step) / cycle(it) / repeat(x, n) infinite (or n-long) generators
batched(it, n) n-sized tuples (3.12+)
collections at a glance.
Type Best for Signature highlight
Counter tally / multiset math .most_common(k), +, -, &, |
defaultdict grouping / accumulating defaultdict(list), defaultdict(int)
deque both-ends queue, ring buffer appendleft, popleft, rotate, maxlen=
namedtuple tiny immutable record .field, ._asdict(), ._replace()
ChainMap layered lookups (defaults + overrides) first-found wins, top to bottom
OrderedDict explicit reordering .move_to_end(key)
Modern time handling.
Goal Modern call Avoid (deprecated/legacy)
Aware “now” in UTC dt.datetime.now(dt.UTC) dt.datetime.utcnow() (deprecated 3.12)
Epoch -> aware UTC dt.datetime.fromtimestamp(ts, dt.UTC) dt.datetime.utcfromtimestamp(ts) (deprecated 3.12)
Real time zone ZoneInfo("Area/City") third-party pytz
Parse ISO-8601 dt.datetime.fromisoformat(s) hand-rolled strptime for ISO input
Convert zone aware.astimezone(ZoneInfo(...)) naive arithmetic across zones

Appendix: Sample Code

Group records with sort + groupby (the classic gotcha)

groupby only groups adjacent equal keys, so you sort by the same key first.

import itertools

rows = [("eng", "ada"), ("sales", "bob"), ("eng", "cal"), ("sales", "dee")]
key = lambda r: r[0]

for team, members in itertools.groupby(sorted(rows, key=key), key=key):
    print(team, [name for _, name in members])
# eng ['ada', 'cal']
# sales ['bob', 'dee']

Tally and group in two lines

from collections import Counter, defaultdict

words = "the cat the dog the bird".split()
print(Counter(words).most_common(1))     # [('the', 3)]

by_len = defaultdict(list)
for w in words:
    by_len[len(w)].append(w)
print(dict(by_len))   # {3: ['the', 'cat', 'the', 'dog', 'the'], 4: ['bird']}

Memoize and pre-fill with functools

from functools import lru_cache, partial, reduce
import operator

@lru_cache(maxsize=None)            # or just @cache (3.9+)
def fib(n):
    return n if n < 2 else fib(n - 1) + fib(n - 2)

fib(30)
print(fib.cache_info())             # CacheInfo(hits=28, misses=31, ...)

add10 = partial(operator.add, 10)
print(add10(5))                     # 15
print(reduce(operator.mul, range(1, 6), 1))   # 120

Aware time, the correct way (store UTC, render local)

import datetime as dt
from zoneinfo import ZoneInfo

now_utc = dt.datetime.now(dt.UTC)               # aware, never utcnow()
local = now_utc.astimezone(ZoneInfo("America/Chicago"))
print(local.strftime("%Y-%m-%d %H:%M %Z"))      # e.g. 2026-07-01 04:30 CDT

# round-trip ISO-8601, with offset preserved
stamp = "2026-07-01T09:30:00-05:00"
when = dt.datetime.fromisoformat(stamp)
print(when.astimezone(ZoneInfo("America/New_York")).isoformat())
# 2026-07-01T10:30:00-04:00

Parse with a compiled regex and named groups

import re

LOG = re.compile(r"(?P<ts>\d{4}-\d{2}-\d{2}) (?P<level>\w+) (?P<msg>.+)")
line = "2026-07-01 ERROR disk full"

m = LOG.match(line)
print(m.groupdict())
# {'ts': '2026-07-01', 'level': 'ERROR', 'msg': 'disk full'}

# replace with a callable
print(re.sub(r"\d+", lambda d: str(int(d.group()) * 2), "a1 b2 c3"))
# a2 b4 c6

A context manager and an immutable dataclass

from contextlib import contextmanager, suppress
from dataclasses import dataclass, field, asdict, replace

@contextmanager
def timer(label):
    import time
    start = time.perf_counter()
    try:
        yield
    finally:
        print(f"{label}: {time.perf_counter() - start:.3f}s")

with timer("work"):
    with suppress(ZeroDivisionError):
        1 / 0                       # swallowed, no traceback

@dataclass(frozen=True, slots=True)
class Point:
    x: int
    y: int = 0
    tags: list[str] = field(default_factory=list)   # safe mutable default

p = Point(1, 2)
print(asdict(p))                    # {'x': 1, 'y': 2, 'tags': []}
print(replace(p, y=9))             # Point(x=1, y=9, tags=[])

Behavior notes

  • itertools is lazy. Calls like chain, islice, and accumulate return iterators that stream on demand; they only build a list when you wrap them in list(...), so they run in constant memory.
  • groupby needs a sort first. It groups only adjacent equal keys, so call sorted(rows, key=key) with the same key before grouping or you will split runs apart.
  • Mutable defaults bite twice. A bare tags: list = [] in a dataclass is shared across all instances; always use field(default_factory=list). The same fix applies to defaultdict(list).
  • Prefer aware datetimes. dt.datetime.now(dt.UTC) is aware; the legacy utcnow() and utcfromtimestamp() are deprecated since 3.12 because they return naive values.
  • zoneinfo replaces pytz. Since 3.9 the stdlib loads real IANA zones with full DST rules; on minimal systems without the OS zone database, pip install tzdata supplies the data.
  • Compile and reuse regexes. re.compile(r"...") once, then reuse the pattern; raw strings keep backslashes intact, and re.sub’s replacement can be a literal string or a callable per match.

References

Standard library documentation (Python 3, current)

Deeper learning / how-to guides