Idiomatic Python
The same program, written twice — once with a C accent, once the way Python wants. Comprehensions, unpacking, the right dict idiom, dataclasses, context managers, EAFP, f-strings, pathlib, and truthiness — each as a before/after pair, with the honest case for when NOT to reach for the idiom. Every snippet run in real CPython.
Concept · Languages & Runtimes. The source ↗
A free, interactive, animated visual explainer of Idiomatic Python — built to be understood, not skimmed.
Questions
- What does "idiomatic Python" (Pythonic) actually mean?
- Writing code the way the language is designed to be written, so the mechanism the reader must decode disappears and only the intent is left. It usually means preferring an expression that names what you want — a comprehension, enumerate(), a dict method — over a manual loop that spells out how to get it. The measure is not cleverness but the Zen of Python: "Readability counts," and "There should be one-- and preferably only one --obvious way to do it." An idiom is fluency: an interviewer reads for i in range(len(xs)) and hears an accent from another language.
- When should you NOT use a list comprehension?
- When it stops being readable. The Zen says "Flat is better than nested," so a comprehension with several for-clauses and multiple conditions — [(i, j) for i in a for j in b if p(i) if q(j)] — is usually clearer as a plain loop. Use a loop, not a comprehension, when the body has side effects (building [print(x) for x in xs] just to print is an anti-pattern), when it needs try/except or an early break, or when the expression is complex enough to need a name. A comprehension is for building a collection from an expression; a loop is for doing things.
- What is the difference between dict.get, setdefault, defaultdict, and Counter?
- They solve four different missing-key problems. dict.get(k, default) reads with a fallback and never inserts — "so that this method never raises a KeyError." setdefault(k, default) reads or inserts in one call, returning the existing or newly inserted value. collections.defaultdict(factory) auto-creates a value on every missing-key access, ideal for grouping. collections.Counter is "a dict subclass for counting hashable objects" — the right tool when the task is literally counting. Pick get for a safe read, setdefault or defaultdict for grouping/accumulating, and Counter for counts.
- What is EAFP vs LBYL, and why is EAFP often safer?
- EAFP ("easier to ask for forgiveness than permission") tries the operation and catches the exception; LBYL ("look before you leap") tests a pre-condition first with an if. The Python glossary notes LBYL "can risk introducing a race condition between 'the looking' and 'the leaping'": if os.path.exists(p) then open(p) can fail because the file may vanish between the check and the open. The EAFP version — try: open(p) except FileNotFoundError — has no gap to race in, and it also avoids the double lookup of if k in d: d[k]. Keep the except narrow so it does not swallow unrelated errors.
- Why is `name = name or default` a bug, and what should you write instead?
- Because or falls back on any falsy value, not just None. Python considers 0, 0.0, "", [], {}, and () all false, so name = name or "friend" replaces a legitimately empty string with "friend", and count = count or 10 turns a real 0 into 10. When None is the only value that means "unset," test for it explicitly: if name is None: name = "friend". PEP 8 is firm that "Comparisons to singletons like None should always be done with is or is not, never the equality operators." Reserve the or-default trick for when every falsy value should genuinely take the default.