| name | python-native |
| description | Push Python stdlib idioms over hand-rolled code. Use whenever the user writes, refactors, optimizes, or reviews Python — including one-liners and code-review feedback. Replaces common reinventions: manual dict counters with `Counter`, `dict.setdefault` loops with `defaultdict`, `isinstance` chains with `singledispatch` or `match`/`case`, hand-rolled memoization with `functools.cache`, `os.path.join` with `pathlib.Path`, `sorted(..., reverse=True)[:k]` with `heapq.nlargest`, manual class boilerplate with `dataclass(slots=True, frozen=True)`, `zip(lst, lst[1:])` with `itertools.pairwise`. Covers Python 3.9-3.14 with per-version reference files. Trigger phrases: "write a Python function", "refactor this Python", "Pythonic way to X", "optimize Python code", "make this more Pythonic", "implement X in Python", or any task whose answer involves Python source. |
Python Native: Use the Standard Library Before Writing Code
Python's standard library is one of the largest and most polished in any language. Most "utility"
code written by AI assistants — custom dispatch tables, ad-hoc memoization, manual grouping loops,
hand-rolled context managers, reinvented namedtuples — is already a one-liner in the stdlib.
Core Principle: Before writing a loop, a class, or a helper, ask:
"Is this already in functools, itertools, collections, operator, or contextlib?"
The answer is "yes" more often than not. Check the stdlib first.
0. The Mental Checklist (Run This Before Writing Any Python)
Every time you're about to write Python, walk this checklist:
- Loop building up a dict/list/set? →
collections.Counter, defaultdict, comprehension, or itertools.
- Dispatching on type or value? →
functools.singledispatch or match/case (3.10+).
- Caching results of a pure function? →
functools.cache (3.9+) or lru_cache.
- Sorting/finding by an attribute or key? →
operator.itemgetter / attrgetter, not lambda.
- Combining or slicing iterables? →
itertools.chain, islice, pairwise, batched.
- Managing setup/teardown? →
contextlib.contextmanager, ExitStack, suppress, nullcontext.
- Holding plain data? →
dataclasses.dataclass, typing.NamedTuple, or types.SimpleNamespace.
- Enumerations or flags? →
enum.Enum, IntEnum, Flag, StrEnum (3.11+).
- Filesystem paths? →
pathlib.Path, never os.path string juggling.
- Priority queue / k-smallest / k-largest? →
heapq.nsmallest, nlargest, heappush.
- Sorted insertion / binary search? →
bisect.insort, bisect_left, bisect_right.
- Running statistics? →
statistics.mean, median, stdev, fmean, correlation.
- Cryptographic / token / password use? →
secrets, never random.
- Random sampling without replacement? →
random.sample (not a manual loop).
- Need an iterator-aware helper? →
itertools.tee, accumulate, starmap, groupby.
If any answer is "yes", stop and use the stdlib feature. Reinventing it is a code smell.
1. functools — The Most Under-Used Module
1.1 singledispatch — Polymorphism Without Class Hierarchies
Replace long isinstance chains and visitor patterns with type-dispatched functions.
from functools import singledispatch
@singledispatch
def serialize(value):
raise TypeError(f"Cannot serialize {type(value).__name__}")
@serialize.register
def _(value: int) -> str:
return str(value)
@serialize.register
def _(value: list) -> str:
return "[" + ", ".join(serialize(v) for v in value) + "]"
@serialize.register(dict)
def _(value):
return "{" + ", ".join(f"{k}:{serialize(v)}" for k, v in value.items()) + "}"
For instance methods, use singledispatchmethod:
from functools import singledispatchmethod
class Renderer:
@singledispatchmethod
def render(self, node):
raise NotImplementedError
@render.register
def _(self, node: str):
return f"<text>{node}</text>"
@render.register
def _(self, node: list):
return "".join(self.render(child) for child in node)
AI anti-pattern: writing if isinstance(x, int): ... elif isinstance(x, list): ....
Use singledispatch. It's discoverable, extensible by registration, and removes the chain.
1.2 cache and lru_cache — Memoization in One Decorator
from functools import cache, lru_cache
@cache
def fib(n: int) -> int:
return n if n < 2 else fib(n - 1) + fib(n - 2)
@lru_cache(maxsize=1024)
def expensive(query: str) -> bytes:
...
cache = lru_cache(maxsize=None), slightly faster — use it when memory is not a concern.
- Arguments must be hashable. Use
frozenset, tuple, or convert before calling.
.cache_info() and .cache_clear() are available on both.
CAVEAT — bound methods: @cache / @lru_cache on instance methods keeps self
strongly referenced through the cache key, preventing garbage collection of the instance
until the cache is cleared. For per-instance memoization, prefer cached_property.
For class-level methods, use bounded lru_cache(maxsize=N), weak-key caches, or
explicit cache_clear().
1.3 cached_property — Lazy, Memoized Attributes
from functools import cached_property
class Dataset:
def __init__(self, path):
self.path = path
@cached_property
def rows(self):
return self._load()
- Stored in
__dict__, so it works only on classes without __slots__ (unless you include __dict__).
- Replace
@property + manual _cache attribute pattern entirely.
1.4 partial and partialmethod — Freezing Arguments
from functools import partial
read_utf8 = partial(open, encoding="utf-8")
debug_log = partial(logger.log, logging.DEBUG)
sorted(items, key=partial(score, weights=w))
Better than lambda because it's introspectable (has .func, .args, .keywords).
1.5 reduce — Last Resort for Accumulators
reduce is rarely needed: sum, min, max, any, all, math.prod cover most cases.
from functools import reduce
from math import prod
prod([1, 2, 3, 4])
reduce(lambda a, b: a * b, [1, 2, 3, 4])
Use reduce only when the operation is genuinely non-builtin and a generator/loop wouldn't be clearer.
1.6 wraps, total_ordering, reduce
from functools import wraps, total_ordering
def trace(fn):
@wraps(fn)
def inner(*args, **kw):
print(fn.__name__, args, kw)
return fn(*args, **kw)
return inner
@total_ordering
class Version:
def __init__(self, parts): self.parts = parts
def __eq__(self, other): return self.parts == other.parts
def __lt__(self, other): return self.parts < other.parts
2. itertools — The Iterator Algebra
Iterators are O(1) memory. Use these primitives instead of building lists and re-iterating.
2.1 Combining Iterables
from itertools import chain, zip_longest, product
list(chain([1, 2], [3, 4], [5]))
list(chain.from_iterable([[1, 2], [3, 4]]))
list(zip_longest("ABCD", "12", fillvalue="-"))
list(product([0, 1], repeat=3))
2.2 Slicing and Windowing
from itertools import islice, takewhile, dropwhile, pairwise
islice(iterable, 10)
list(pairwise([1, 2, 3, 4]))
list(takewhile(lambda x: x < 5, [1, 3, 5, 2]))
For Python 3.12+: itertools.batched(iterable, n) chunks an iterable into n-tuples.
2.3 Grouping (Replaces Manual defaultdict Loops)
from itertools import groupby
from operator import itemgetter
rows = sorted(rows, key=itemgetter("dept"))
for dept, group in groupby(rows, key=itemgetter("dept")):
print(dept, list(group))
Common bug: forgetting that groupby only groups adjacent equal keys. Sort first.
2.4 Accumulators and Counters
from itertools import accumulate, count, cycle, repeat
list(accumulate([1, 2, 3, 4]))
list(accumulate([1, 2, 3, 4], initial=100))
list(accumulate([3, 1, 4, 1, 5], max))
for i in count(start=1): ...
for color in cycle(["r", "g", "b"]): ...
2.5 Combinations and Permutations
from itertools import combinations, permutations, combinations_with_replacement
list(combinations("ABC", 2))
list(permutations("ABC", 2))
Replace any nested loop that filters duplicates with the right combinatoric primitive.
2.6 tee and starmap
from itertools import tee, starmap
a, b = tee(iterator)
list(starmap(pow, [(2, 3), (10, 2)]))
Caveat on tee: when the two consumers advance unevenly, tee accumulates an internal
buffer of every element produced since the slower consumer last advanced. On long iterators
with skewed consumption, this can blow memory. Use tee only when consumers stay roughly in
step, or materialize the iterator into a list explicitly.
3. collections — Data Structures You Should Default To
3.1 Counter — Frequency Counting in One Line
from collections import Counter
c = Counter("abracadabra")
c.most_common(3)
c + Counter("aaa")
c & Counter("abxxx")
+c
AI anti-pattern: freq = {}; for x in xs: freq[x] = freq.get(x, 0) + 1. Use Counter(xs).
3.2 defaultdict — Auto-Initialized Dicts
from collections import defaultdict
groups = defaultdict(list)
for user, action in events:
groups[user].append(action)
graph = defaultdict(set)
for a, b in edges:
graph[a].add(b)
graph[b].add(a)
Replaces the dict.setdefault pattern and the if key not in d: d[key] = [] antipattern.
3.3 deque — O(1) Append/Pop at Both Ends
from collections import deque
q = deque(maxlen=1000)
q.appendleft(x); q.pop(); q.rotate(-1)
Use deque for:
- BFS queues
- Sliding windows
- Rolling logs / ring buffers (
maxlen)
- Any FIFO —
list.pop(0) is O(n).
3.4 ChainMap — Layered Lookups
from collections import ChainMap
config = ChainMap(cli_args, env_vars, defaults)
Avoid manually merging dicts to implement precedence; ChainMap does it without copying.
3.5 namedtuple (and Why Often dataclass Wins)
from collections import namedtuple
Point = namedtuple("Point", "x y")
p = Point(3, 4)
p.x, p.y, p._asdict(), p._replace(x=10)
- Use
namedtuple for immutable lightweight records.
- Use
typing.NamedTuple for type hints.
- Use
dataclass(frozen=True, slots=True) when you want defaults, methods, or richer behavior.
4. operator — Functions for Operators
Replaces tiny lambdas with introspectable, pickleable callables.
from operator import itemgetter, attrgetter, methodcaller, mul
sorted(people, key=attrgetter("age"))
sorted(rows, key=itemgetter("dept", "salary"))
list(map(methodcaller("strip"), lines))
list(map(mul, xs, ys))
itemgetter("a", "b") returns a tuple — useful for sort keys.
methodcaller("split", ",") is callable; great in map, filter, sort.
5. contextlib — Context Managers Beyond with open(...)
5.1 @contextmanager — Build One in 4 Lines
from contextlib import contextmanager
@contextmanager
def timer(label):
start = time.perf_counter()
try:
yield
finally:
print(f"{label}: {time.perf_counter() - start:.3f}s")
with timer("load"):
data = load()
5.2 suppress, nullcontext, closing, redirect_stdout
from contextlib import suppress, nullcontext, closing, redirect_stdout
import io
with suppress(FileNotFoundError):
os.remove(path)
cm = open(path) if log else nullcontext()
with cm as f: ...
with closing(urllib.request.urlopen(url)) as resp: ...
buf = io.StringIO()
with redirect_stdout(buf):
noisy_function()
5.3 ExitStack — Dynamic Stacks of Context Managers
from contextlib import ExitStack
with ExitStack() as stack:
files = [stack.enter_context(open(p)) for p in paths]
process(files)
Replaces deeply nested with blocks and conditional setup/teardown.
6. dataclasses — The Right Default for Data Classes
from dataclasses import dataclass, field, asdict, replace
@dataclass(frozen=True, slots=True, kw_only=True)
class Order:
id: int
items: tuple[str, ...] = ()
total: float = 0.0
o = Order(id=1, items=("a", "b"), total=9.99)
o2 = replace(o, total=10.5)
asdict(o)
frozen=True blocks attribute rebinding and auto-generates __hash__ — but the hash
succeeds only when every field value is hashable. A list[str] field will pass type
checking yet raise TypeError: unhashable type: 'list' at hash time. For hashable
records, use immutable field types (tuple, frozenset, str, numbers) or mark mutable
fields field(compare=False, hash=False) to keep them out of the generated __hash__
and __eq__.
slots=True → smaller memory, faster attribute access, no accidental new attrs. Use
slots for stable leaf data models.
kw_only=True → forces keyword args; prevents positional-arg bugs.
field(default_factory=list) → never use a mutable default directly (default values
are evaluated once at class-definition time, so a bare items: list = [] would share
one list across all instances).
For inherited or computed fields, use field(init=False, default=...) and __post_init__.
CAVEAT — slots edge cases: multiple inheritance with slots can hit layout conflicts
when multiple bases declare their own __slots__ — test before enabling on
hierarchical classes. Breaks weakref.ref() unless you pass weakref_slot=True
(3.11+). Breaks @cached_property because slots prevents writing to __dict__.
7. pathlib — Stop Using os.path Strings
from pathlib import Path
p = Path("/var/log") / "app" / "today.log"
p.parent, p.name, p.stem, p.suffix
p.exists(), p.is_file(), p.with_suffix(".bak")
p.read_text(encoding="utf-8")
p.write_text("hello", encoding="utf-8")
list(p.parent.glob("*.log"))
list(p.parent.rglob("*.py"))
Path.home(), Path.cwd()
pathlib is OS-agnostic, returns rich objects, and supports operators. There is no good reason
to use os.path.join in new code.
8. heapq and bisect — Specialized Algorithms
8.1 heapq — Priority Queues and Top-K
import heapq
heap = []
for x in xs:
heapq.heappush(heap, x)
smallest = heapq.heappop(heap)
heapq.nsmallest(5, items, key=lambda x: x.cost)
heapq.nlargest(10, scores)
AI anti-pattern: sorted(xs)[:k] to get the k smallest, when k is much smaller than n.
Use nsmallest(k, xs) — O(n log k), which beats sort for small k and for streaming inputs.
When k approaches n (rule of thumb: k ≥ n/3) or you also need the rest of the sequence
sorted, plain sorted(xs)[:k] is competitive or faster.
8.2 bisect — Sorted Lists Without Re-Sorting
import bisect
bisect.insort(sorted_list, value)
i = bisect.bisect_left(sorted_list, value)
j = bisect.bisect_right(sorted_list, value)
Use bisect for sorted in-memory indexes, range queries, and rank lookups.
9. enum — Real Enumerations
from enum import Enum, IntEnum, Flag, auto
class Status(Enum):
PENDING = auto()
ACTIVE = auto()
DONE = auto()
class Permission(Flag):
READ = auto()
WRITE = auto()
EXEC = auto()
ALL = READ | WRITE | EXEC
Permission.READ | Permission.WRITE
- Use
Enum for closed sets of named constants.
- Use
IntEnum only when integer interop matters.
- Use
StrEnum (3.11+) for string-based enums (no manual .value everywhere).
- Use
Flag / IntFlag for bitfields.
10. typing — Use the Modern Forms
from typing import Protocol, NamedTuple, TypedDict, Literal, Annotated, Self, TypeAlias
class Closeable(Protocol):
def close(self) -> None: ...
class User(TypedDict):
id: int
name: str
Mode = Literal["r", "w", "a"]
class Query:
def where(self, **kw) -> Self: ...
def order_by(self, key) -> Self: ...
- Built-in generics:
list[int], dict[str, int], tuple[int, ...] (3.9+) — no need for List, Dict.
X | Y union syntax (3.10+) replaces Union[X, Y]. X | None replaces Optional[X].
TypeAlias (3.10+) for documented aliases; type statement (3.12+).
11. match / case — Structural Pattern Matching (3.10+)
def evaluate(node):
match node:
case {"op": "add", "left": l, "right": r}:
return evaluate(l) + evaluate(r)
case [first, *rest]:
return [evaluate(first), *map(evaluate, rest)]
case int() | float():
return node
case Point(x=0, y=y):
return f"on y-axis at {y}"
case _:
raise ValueError(node)
- Patterns include literals, types, sequences, mappings, class patterns, OR (
|), guards (if).
- Names in patterns bind —
case x: matches anything and binds. Use case _: for wildcard.
- Use class patterns to destructure dataclasses by attribute.
12. Walrus Operator (:=) — Use Where It Reduces Repetition
while chunk := f.read(4096):
process(chunk)
results = [(x, y) for x in xs if (y := compute(x)) is not None]
if (match := pattern.search(line)) is not None:
use(match.group(1))
Don't use it just to be clever; use it when it removes a duplicated computation.
13. f-strings — Use the Full Format Spec
f"{value:>10.2f}"
f"{value:,.0f}"
f"{value:.2%}"
f"{n:#06x}"
f"{name!r}"
f"{obj=}"
f"{dt:%Y-%m-%d}"
f"{a + b = :,}"
f-strings (3.12+, PEP 701) allow reusing the same quote character inside the expression:
f"{ ", ".join(parts) }"
14. Generators and Comprehensions
- Prefer generator expressions for one-shot iteration:
sum(x*x for x in xs) — no list built.
- Use
yield from for delegation: yield from inner() is equivalent to a for loop.
- A function with
yield is already an iterator; no class needed.
- Generators are O(1) memory — chain them with
itertools.chain for streaming pipelines.
def lines_of(path):
with open(path, encoding="utf-8") as f:
yield from f
def non_empty(it):
return (line.strip() for line in it if line.strip())
for line in non_empty(lines_of("big.txt")):
process(line)
15. Built-in Functions That Are Often Forgotten
| Function | What it does that you might re-implement |
|---|
any(p(x) for x in xs) | Short-circuit "exists" — don't loop and return early manually. |
all(...) | Short-circuit "for all". |
sum(iter, start=0) | Numeric sums with optional start; start cannot be a string (use ''.join()); for concatenating iterables use itertools.chain.from_iterable. Use math.prod for products. |
min(iter, key=, default=) / max(...) | Use key= instead of pre-sorting. default= for empty iterables. |
sorted(iter, key=, reverse=) | Stable sort. key is computed once per element. |
reversed(seq) | O(1) lazy iterator over a reversible sequence or any object with __reversed__; generators are not reversible — materialize to a list first if needed. |
enumerate(iter, start=N) | Pass start= instead of i + offset. |
zip(*iters, strict=True) | strict=True (3.10+) raises on length mismatch — use it. |
divmod(a, b) | Returns (quotient, remainder) — replaces two operations. |
pow(b, e, mod) | Modular exponentiation — vastly faster than (b**e) % mod. |
round(x, n) | Banker's rounding by default; pass ndigits for precision. |
iter(callable, sentinel) | Loops calling callable() until it returns sentinel. |
next(iter, default) | Pass a default to avoid StopIteration handling. |
vars(obj) | Inspect attributes — sometimes simpler than __dict__. |
getattr(obj, name, default) | Don't try/except AttributeError — pass default. |
hash(x) | Test hashability quickly. |
id(x) / is | Identity, not equality. Don't use == for None / singletons. |
16. Other Stdlib Modules Worth Reaching For
| Module | Use when… |
|---|
statistics | Mean, median, stdev, mode, correlation, linear regression — no need for NumPy on small data. |
math | gcd, lcm, isclose, prod, comb, perm, hypot. |
secrets | Tokens, passwords, anything security-sensitive. Never random here. |
random | Non-security randomness. Use random.choices (weighted), sample (no replacement). |
hashlib | Hashing. Use hashlib.file_digest (3.11+) for streaming file hashes. |
struct | Pack/unpack binary data. |
io.StringIO, io.BytesIO | In-memory file-like objects for testing or pipelines. |
tempfile | TemporaryDirectory, NamedTemporaryFile. Don't hand-roll temp files. |
shutil | File ops (copy, move, rmtree, which, make_archive). |
subprocess | subprocess.run(..., check=True, capture_output=True, text=True). |
argparse | Don't parse sys.argv by hand. |
logging | Use the logging tree — don't sprinkle print(). |
json | Use default= hook for custom serializers; indent=2 for human output. |
csv | csv.DictReader / DictWriter — never split(","). |
sqlite3 | Built-in DB. Great for caches, tests, prototypes. |
concurrent.futures | ThreadPoolExecutor / ProcessPoolExecutor — don't manage threads by hand. |
asyncio | asyncio.run, gather, TaskGroup (3.11+), to_thread. |
weakref | WeakValueDictionary, WeakSet — caches that don't keep objects alive. |
types.SimpleNamespace | Quick attribute-bag without defining a class. |
types.MappingProxyType | Read-only view of a dict. |
abc | ABC, @abstractmethod for interfaces. |
dataclasses | See §6. |
typing | See §10. |
textwrap | dedent, indent, fill, shorten. |
string | string.Template, string.ascii_letters, digits, Formatter. |
unicodedata | Normalize, categorize, strip accents. |
decimal | Exact decimal arithmetic (money). Never use float for currency. |
fractions | Exact rationals. |
ipaddress | Parse and manipulate IPs/networks; don't regex them. |
urllib.parse | URL parsing / encoding; don't string-manipulate URLs. |
inspect | Introspect signatures, source, callables. |
traceback | Format/print tracebacks programmatically. |
pprint | Readable structure dumps. |
17. Things AI Routinely Reinvents (Stop Doing These)
| Reinvention | Replace with |
|---|
Manual freq = {} loop | Counter(iterable) |
dict.setdefault(k, []).append(v) | defaultdict(list) |
if x not in d: d[x] = … | dict.setdefault or defaultdict |
sorted(x)[:k] for k-smallest | heapq.nsmallest(k, x) |
sorted(x, reverse=True)[:k] for k-largest | heapq.nlargest(k, x) |
list.pop(0) for queues | collections.deque |
isinstance chains | functools.singledispatch or match |
lambda p: p.attr for sort | operator.attrgetter("attr") |
| Hand-rolled memo dict | functools.cache / lru_cache |
| Hand-rolled context manager class | @contextlib.contextmanager |
| Manual flattening with nested loops | itertools.chain.from_iterable |
| Manual pairwise loop | itertools.pairwise (3.10+) |
| Manual chunking loop | itertools.batched (3.12+) |
Hand-rolled Result/Maybe for dispatch | match / case |
class Foo: __init__: self.x=x; self.y=y; __repr__=... | @dataclass |
os.path.join(...) strings | pathlib.Path(...) |
Manual try/except for "absent file" deletion | contextlib.suppress(FileNotFoundError) |
| Hand-rolled CLI parsing | argparse |
random.SystemRandom() for tokens | secrets.token_urlsafe(n) |
18. Version Support Strategy (3.9 → 3.14)
This skill works on Python 3.9+. When a feature is newer, prefer it on supported versions but fall
back to the stdlib equivalent on older ones. Always check the target's Python version first.
- Look in
references/python-3.X.md for the precise list of features introduced or changed.
- When unsure of version:
python --version, or read pyproject.toml (requires-python).
- Use
sys.version_info >= (3, 11) for runtime branches; do not rely on try/except ImportError
unless the feature actually moved between modules.
PEP 594 dead-battery removals in 3.13
If your code imports any of these, plan a replacement before upgrading. The 19 modules removed per PEP 594 (Python 3.13): aifc, audioop, cgi, cgitb, chunk, crypt, imghdr, mailcap, msilib, nis, nntplib, ossaudiodev, pipes, sndhdr, spwd, sunau, telnetlib, uu, xdrlib. Separately removed: lib2to3 (the 2to3 source-migration tool).
Compatibility Cheat-Sheet (Highlights)
| Feature | Introduced |
|---|
functools.cache, dict | dict merge, removeprefix/suffix, built-in generics (list[int]) | 3.9 |
match / case, X | Y unions, zip(strict=True), dataclass(slots=, kw_only=), itertools.pairwise, parenthesized context managers | 3.10 |
tomllib, ExceptionGroup, except*, Self, StrEnum, TaskGroup, hashlib.file_digest, exception notes (__notes__) | 3.11 |
itertools.batched, type statement, PEP 695 generics, @override, PEP 701 f-strings, per-interpreter GIL (PEP 684), comprehension inlining, pathlib.Path.walk, random.binomialvariate | 3.12 |
Free-threaded build (PEP 703 experimental), JIT (PEP 744 experimental), dbm.sqlite3, new REPL, itertools.batched(strict=), copy.replace, typing.TypeIs/ReadOnly, TypeVar defaults (PEP 696), os.process_cpu_count, removal of 19 dead-battery modules | 3.13 |
Deferred annotations (PEP 649) + annotationlib, t-strings (PEP 750), concurrent.interpreters (PEP 734), InterpreterPoolExecutor, free-threaded officially supported (PEP 779), pathlib.Path.copy/move | 3.14 |
See the references/ directory for the full feature list per version.
19. Reference Files
For per-version deep dives, consult:
references/python-3.9.md — built-in generics, cache, removeprefix/suffix, dict union
references/python-3.10.md — match/case, X | Y, zip(strict=), dataclass(slots=), parenthesized with
references/python-3.11.md — ExceptionGroup, Self, StrEnum, tomllib, TaskGroup, fine-grained tracebacks
references/python-3.12.md — PEP 695 generics, type statement, @override, itertools.batched
references/python-3.13.md — free-threaded build, JIT, dbm.sqlite3, removals
references/python-3.14.md — PEP 649 deferred annotations + annotationlib, t-strings (PEP 750), concurrent.interpreters (PEP 734), PEP 779 free-threaded official, pathlib.Path.copy/move
references/stdlib-decision-tree.md — "I want to do X; which module?" lookup table
references/anti-patterns.md — full catalog organized by Correctness / Maintainability / Readability / Performance / Security, expanded from QuantifiedCode's Python Anti-Patterns book and modernized for Python 3.9+
Sources
Content in this skill was verified against the official "What's New in Python" pages:
For PEPs: https://peps.python.org/ (e.g. PEP 585 → https://peps.python.org/pep-0585/).
For module references: https://docs.python.org/3/library/.html.
Quick Reference Card
BEFORE writing a loop: Can it be a comprehension / itertools call?
BEFORE writing a class: Can it be @dataclass / NamedTuple / SimpleNamespace?
BEFORE writing isinstance: Can it be singledispatch / match?
BEFORE writing a cache dict: Use functools.cache.
BEFORE writing os.path: Use pathlib.Path.
BEFORE writing lambda key: Use operator.itemgetter / attrgetter.
BEFORE writing try/except: Use contextlib.suppress where applicable.
BEFORE manual freq counting: Use collections.Counter.
BEFORE list.pop(0): Use collections.deque.
BEFORE sorted(...)[:k] (smallest): Use heapq.nsmallest.
BEFORE sorted(..., reverse=True)[:k]: Use heapq.nlargest.
WHEN dispatching: functools.singledispatch or match/case.
WHEN in doubt: Search the stdlib index before writing code.