| name | python-practices |
| description | Apply whenever writing or reviewing Python code — prescriptive best practices for Python 3.14: which idioms to prefer, which anti-patterns to avoid, and which modern language features to reach for first. |
Language Standard
Python 3.14 is the target. Always prefer the most modern idiomatic form available in 3.14.
When unsure about current best practice for a topic, web-search before writing.
Living Document
When you discuss, teach, or implement a Python pattern that reveals a best practice not yet listed here, add it to the appropriate section before the conversation ends.
1. Types & Truthiness
- Use
isinstance() over type() for type checks — respects inheritance and subclasses
- Use
is None / is not None, never == None
- Prefer truthiness checks:
if items: not if len(items) > 0:; if not items: not if len(items) == 0:
- Use
x or default for falsy-fallback values (understand short-circuit: returns first truthy or last value)
- Use
is only for singletons: None, True, False
2. Modern Syntax
- Walrus operator
:= (3.8+) — assign inside expressions to avoid double evaluation:
if (n := len(data)) > 10: or while chunk := f.read(8192):
- Match/case structural pattern matching (3.10+) — prefer over long if/elif chains for multi-way dispatch on type or structure; use:
- Guard clauses:
case x if x > 0:
- Sequence patterns:
case [first, *rest]:
- Mapping patterns:
case {"type": "click", "pos": pos}:
- Class patterns:
case Point(x=0, y=0):
as patterns: case [_, *_] as full_list:
- Dotted-name value patterns (not captures):
case Color.RED:
- Catch-all:
case _:
zip(..., strict=True) (3.10+) — always use when zipping sequences that must be equal length
dict | other / |= merging (3.9+) — prefer over {**a, **b} unless interleaving inline keys
ExceptionGroup + except* (3.11+) — use for concurrent/async error aggregation where multiple independent exceptions occur
- Chained comparisons —
1 < x < 10 instead of x > 1 and x < 10; x is evaluated only once
3. Data Structures
collections.Counter — for counting hashable items; missing keys return 0 (not KeyError); use .most_common(n), and arithmetic operators (+ adds, - subtracts dropping negatives, & keeps minimum, | keeps maximum)
collections.defaultdict — for auto-initializing missing keys (defaultdict(list), defaultdict(int)); prefer over manual setdefault loops
typing.NamedTuple class syntax — prefer over collections.namedtuple; supports defaults and type annotations; use _replace() for modified copies, _asdict() for dict conversion
dict.get(key, default) — prefer over if key in d: ... d[key]
dict.pop(key, None) — prefer over check-then-delete
dict.setdefault(key, val) — inserts only if key absent; never overwrites
- Tuple as dict key — for composite keys; lists are not hashable
- Lists as stacks —
append()/pop() is O(1); use collections.deque for queues (pop(0) is O(n))
- Avoid
[[0]*n]*m — all rows share the same list object; use [[0]*n for _ in range(m)]
- Dict views (
d.keys(), d.values(), d.items()) are live — they reflect later mutations
4. Iteration
enumerate(iterable, start=N) — always prefer over manual index tracking
zip() — for parallel iteration; zip(*pairs) for unzipping / transposing
any() / all() — short-circuit boolean queries; any([]) == False, all([]) == True (vacuous truth)
next(iterator, default) — use the default to avoid StopIteration
- Generator expressions over
map()/filter() — equally lazy, more readable; use map/filter only when directly passing to higher-order functions
5. Comprehensions
- Prefer comprehensions over explicit loops for transformations
- Filter form:
[x for x in items if cond] — shorter list
- Ternary form:
[x if cond else y for x in items] — same length, transformed values (these are different, don't confuse them)
- Flatten with nested comprehension:
[cell for row in matrix for cell in row]
- Invert a mapping:
{v: k for k, v in d.items()}
- Comprehension variables do not leak into enclosing scope (Python 3); walrus
:= is the exception — it leaks
6. Functions
- Mutable default arguments — always use
None sentinel, construct inside: def f(items=None): items = [] if items is None else items; default objects are shared across all calls
- Keyword-only arguments (
*) — use def f(a, *, b): to force callers to name b; good for Boolean flags and optional config
- Positional-only arguments (
/) — use def f(a, /): to prevent callers from using keyword form; good for library APIs
functools.singledispatch — for type-based dispatch; register implementations per type, decouples type-specific logic from the base function
functools.partial() — for specializing callables without writing wrapper functions
operator.itemgetter() / attrgetter() / methodcaller() — prefer over lambdas as key/sort functions
- Avoid assigning lambdas to variables — use
def instead; lambdas show <lambda> in tracebacks
for/else — else runs when loop completes without break; idiomatic "search found nothing" pattern, no flag variable needed
functools.reduce() — for cumulative operations with an initial value; supply initial argument to handle empty sequences
7. Error Handling
- Always catch specific exception types; never bare
except: (catches SystemExit, KeyboardInterrupt, etc.)
except (ValueError, TypeError): — multiple types in one clause
try/except/else/finally — put "only-if-no-exception" code in else, not after the try block
- Bare
raise inside except re-raises preserving the original traceback; raise e adds an extra frame
raise NewError(...) from original — explicit exception chaining; sets __cause__; never silently drop the original with raise NewError() from None unless intentional
- Inherit custom exceptions from
Exception, not BaseException; add custom attributes for structured error context
contextlib.suppress(ExcType) — prefer over try: ... except ExcType: pass
ExceptionGroup / except* (3.11+) — for multiple simultaneous errors (e.g., asyncio.TaskGroup)
8. Strings
- f-strings for all string formatting (3.6+); avoid legacy
% formatting and .format()
str.join() for concatenating iterables — ", ".join(words); never s += word in a loop
- Raw strings
r"" for regex patterns and Windows paths
.split() without arguments splits on any whitespace, handles multiple spaces/tabs/newlines
.strip() / .lstrip() / .rstrip() for whitespace trimming
9. Context Managers
with for all resource management (files, locks, connections) — guarantees cleanup even on exception
@contextlib.contextmanager for simple generator-based CMs: code before yield is setup; always wrap yield in try/finally for guaranteed teardown
contextlib.ExitStack for composing a dynamic number of context managers without nested with statements
__exit__ returning True suppresses the exception; returning None/False propagates it