The first comprehension I ever saw was [x*x for x in nums], and I honestly closed the tab. It looked like punctuation soup. What made them click was watching one get built out of a loop I already understood, line by line. That's the whole plan here — no soup until we can see the loop inside it.
sizes = []
for name in filenames:
sizes.append(len(name))
Three parts: an empty list, a for line, and an append. A comprehension is the same three parts, rearranged — the expression first, the loop after it:
sizes = [len(name) for name in filenames]
Read it as: "a list of len(name), for each name in filenames." The same list comes out. What surprised me is that it's easier to read later, because the thing being built and the name it gets sit on one line.
Real lists need filtering. The loop version, picking the spreadsheets out of a folder listing:
csvs = []
for name in filenames:
if name.endswith(".csv"):
csvs.append(name)
A comprehension puts the if at the end and keeps only what passes:
csvs = [name for name in filenames if name.endswith(".csv")]
You can transform while you filter. Here I'm slicing off the last four characters to drop the extension:
stems = [name[:-4] for name in filenames if name.endswith(".csv")]
For "report.csv" that gives "report". One line, and it still reads left to right: keep the CSVs, take off the .csv.
Now the honest half. Comprehensions build lists. When the loop's job is something else — writing a file, printing, alerting someone — a comprehension is the wrong tool, even though it technically runs:
for line in open("server.log", encoding="utf-8"):
if "ERROR" in line and "healthcheck" not in line:
print(line.strip())
This loop never builds anything; its job is the printing. Writing [print(line) for line in ...] would work and produce a list of Nones nobody wants — a comprehension doing a loop's job, and worse at it.
My rule, after some regret: if the body is more than a line or two, or there's more than one if, or it has side effects, write the loop. A comprehension exists to be understood at a glance; the moment I can't glance at it, it has failed at the one thing it's for.
One limit worth meeting in person: a comprehension builds the whole list in memory, right now. I found that out on a 2 GB log file that vanished into swap. When the list is huge and you only loop over it once, swapping the square brackets for round ones — (len(line) for line in log) — gives you a generator that produces values as they're asked for. Same inside, no memory wall. I reach for it maybe twice a year, but when I need it, I need it.
One last honesty note: comprehensions can take a second for for nested work, something like [x + y for x in a for y in b]. I use that roughly once a year and look it up every time. The everyday comprehension is the single one — expression, loop, condition — and that one is worth wearing in.