03 / 04

What are list, dict, and set comprehensions, and why are they often preferred over explicit loops?

Difficulty: 3/10
Comprehensions & Generator Expressions, List, Dict and Set Comprehensions, Readability and Performance

List, dict and set comprehensions

A comprehension is an expression that builds a collection from an iterable in one declarative statement: [expr for x in it if cond] for a list, {k: v for ...} for a dict, and {expr for ...} for a set. Structurally it is the same as a for loop with an optional filter and an append/assignment, but it states 'what the result is' rather than 'how to assemble it'. Multiple for clauses read left to right in the same order as the equivalent nested loops, and a conditional expression (x if c else y) in the output position is different from a filter (if c) at the end.

javascript

Why they're preferred: (1) readability - for transformations and filters the intent is visible in one line instead of being spread across initialization, loop, condition and append; (2) fewer mutable-state bugs, because there is no half-built list variable hanging around; (3) usually faster than the append loop, because the interpreter uses a dedicated list-append instruction and avoids a method lookup and call per item. The size of the gain varies by version and workload, so I say 'typically faster' and verify with timeit rather than quote numbers. Python 3.12 inlined comprehensions (PEP 709), which reduced their overhead further without changing semantics.

Scope and semantics details: in Python 3 the loop variable of a comprehension does not leak into the enclosing scope (unlike a for statement, and unlike Python 2 list comprehensions). A walrus assignment inside a comprehension, however, binds in the containing scope by design. In a dict comprehension duplicate keys silently keep the last value, which matters when inverting a mapping with non-unique values; a set comprehension has no defined order.

When not to use them, and common mistakes: don't use a comprehension for side effects ([print(x) for x in items] builds a throwaway list - use a for loop); don't cram multiple nested loops, conditions and walrus operators into one line - once it needs more than about two clauses or stops reading like a sentence, use a loop or a helper function; and for very large or unbounded data prefer a generator expression, because a list comprehension materializes everything in memory. Another gotcha is that {} is an empty dict, so an empty set is set(). Alternatives such as map, filter, itertools and numpy have their place, but for general-purpose transformations a comprehension is the most Pythonic default.

Scenario Questions

0-2 years experience

  1. 1Rewrite a loop that collects the lengths of all strings longer than three characters into a comprehension.
  2. 2What is the difference between {} and {x for x in []}, and how do you create an empty set?

2-5 years experience

  1. 1Explain where the condition goes in [x if c else y for ...] versus [x for ... if c]. Write an example of each.
  2. 2Flatten a list of lists and invert a dictionary with comprehensions. What happens when the dictionary has duplicate values?

5-8 years experience

  1. 1A teammate wrote a three-level nested comprehension with two conditions and a walrus operator. How do you decide whether to keep it in code review, and how would you refactor it?
  2. 2Does a comprehension's loop variable leak into the surrounding scope? What about a walrus assignment inside it, and what did PEP 709 change in Python 3.12?

8+ years experience

  1. 1Someone claims comprehensions are always faster than loops. How would you test that rigorously, and when does the claim break down (e.g. per-element Python function calls, large memory)?
  2. 2Explain how comprehensions are compiled (their own function scope before 3.12, inlined in 3.12) and what that means for tracebacks, debuggers and class-body scoping.

Follow-up Questions

  • Why does a walrus assignment inside a comprehension leak into the enclosing scope while the loop variable doesn't?
  • When would you switch from a comprehension to a plain loop or helper function?
Share

Share via WhatsApp, X, Facebook, LinkedIn or copy link. Open Graph preview enabled.