Comprehensions
A comprehension builds a list or a dictionary in a single expression. It is the most distinctively Python thing in this tutorial, it appears constantly in framework code, and there is no clean equivalent in Java or C# unless you count streams and LINQ.
The pattern
The long way:
names = ["priya", "arun", "meera"]
capitalised = []
for name in names:
capitalised.append(name.title())
print(capitalised)The comprehension:
capitalised = [name.title() for name in names]Same result, one line. Read it left to right as a sentence:
"give me name.title(), for every name in names"
The structure is always:
[ what to produce for each item in the source ]The thing you want comes first, the loop comes after. That inversion is what makes it look strange for a week and obvious afterwards.
Filtering
Add an if on the end to keep only some items.
tickets = ["P1", "P3", "P2", "P1", "P4"]
urgent = [t for t in tickets if t == "P1"]
print(urgent)Read it as: "give me t, for every t in tickets, where t is P1".
You can transform and filter at once:
lengths = [len(t) for t in tickets if t != "P4"]Dictionary comprehensions
Same idea, curly braces, and a key: value pair.
members = ["priya", "arun", "meera"]
lookup = {name: len(name) for name in members}
print(lookup){'priya': 5, 'arun': 4, 'meera': 5}A pattern you will use often — turning a list of records into a lookup by id:
tickets = [
{"id": 1, "severity": "P1"},
{"id": 2, "severity": "P3"},
]
by_id = {t["id"]: t for t in tickets}
print(by_id[2])That replaces a loop and gives you instant lookup by key.
Inverting a dictionary is a two-liner people often write as ten:
codes = {"critical": "P1", "high": "P2"}
flipped = {v: k for k, v in codes.items()}Set comprehensions
Curly braces with no colon gives a set, which removes duplicates for you.
owners = {t["owner"] for t in tickets}Where you will meet them in agent code
They are everywhere, usually doing one of three jobs.
Extracting one field from a list of records:
contents = [m["content"] for m in messages]Building the arguments for a fan-out:
results = await asyncio.gather(*[summarise(d) for d in docs])Turning your tool functions into the list a framework wants:
tools = [tool(f) for f in [get_weather, search_web, lookup_order]]Recognising the shape is most of the benefit. Once [x for y in z] reads as one idea rather than three, framework source stops looking dense.
Generator expressions
Swap the square brackets for round ones and you get a generator — values produced one at a time instead of a list built in memory.
squares_list = [n * n for n in range(1_000_000)]
squares_gen = (n * n for n in range(1_000_000))Use round brackets when you only intend to loop once, especially with sum, any, all or max:
total = sum(len(line) for line in read_lines("huge.log"))
has_p1 = any(t == "P1" for t in tickets)When not to use one
A comprehension is for one simple transformation. The moment it needs a second thought, write the loop.
Do not write this:
result = [transform(x) if check(x) else fallback(x) for x in items if x is not None and x.enabled]Write this:
result = []
for x in items:
if x is None or not x.enabled:
continue
result.append(transform(x) if check(x) else fallback(x))The second is longer and better. Nobody has ever been thanked for a clever one-liner they had to explain.
Two rules that hold up well:
- if it does not fit comfortably on one line, use a loop
- if it has more than one
for, use a loop
Coming from Java or C#
You have met this idea, just spelled very differently.
| Task | Java | C# | Python |
|---|---|---|---|
| Transform | .stream().map(f).toList() | .Select(f).ToList() | [f(x) for x in xs] |
| Filter | .stream().filter(p).toList() | .Where(p).ToList() | [x for x in xs if p(x)] |
| Both | .filter(p).map(f) | .Where(p).Select(f) | [f(x) for x in xs if p(x)] |
| To a map | Collectors.toMap(k, v) | .ToDictionary(k, v) | {k(x): v(x) for x in xs} |
| Lazy | Stream | IEnumerable | (f(x) for x in xs) |
The one habit to adjust: LINQ and streams read in pipeline order — source first, then operations. A comprehension puts the result first and the source second. Read the middle of the line to find out what it is looping over.
Common mistakes
- Square brackets when you wanted a generator, building a huge list you only walk once.
- Cramming in too much. If you paused to work out what it does, so will everyone else.
- Forgetting
.items()when looping a dictionary:for k, v in d.items(), notfor k, v in d. - Using one for side effects.
[print(x) for x in xs]builds a throwaway list ofNone. Use a plainforloop when you want an action, not a value.
Practise this
- Turn a list of prices into a list of prices with 18% tax added.
- From a list of ticket dictionaries, build a list of the ids of the P1 ones only.
- Build a lookup dictionary keyed by
idfrom a list of records. - Rewrite one of your comprehensions as a plain loop, and decide honestly which reads better.