The default for sequence transformation
If you're doing "for each x, produce y" — the default in Python is a comprehension. [x*2 for x in xs] for a list, (x*2 for x in xs) for a generator. The for-loop-then-append pattern is what you reach for when comprehensions don't fit, not the other way around.
map and filter — usually not Pythonic anymore
list(map(lambda x: x*2, xs)) works but reads worse than [x*2 for x in xs]. list(filter(lambda x: x > 0, xs)) reads worse than [x for x in xs if x > 0]. The exception: when the function is already named (list(map(str.upper, words))) — that's competitive.
Generators for "process a sequence once"
If the result of a comprehension is going to be consumed once and never re-iterated, swap brackets for parens: sum(x*x for x in xs). Same logic, no intermediate list, short-circuits where possible. The big win in code that processes large or infinite sequences.
The skill — recognizing the shape
The pythonic skill isn't "use comprehensions everywhere." It's recognizing when a transformation fits one. When the inner logic is two lines or has multiple branches, write a real for-loop or extract a helper function. Pythonic is "match the syntax to the complexity."
sum, any, all, max) or when the source is huge. Reach for explicit loops only when the per-element logic outgrows a single expression.
A comprehension declares a resulting collection. Logging, several mutations, or branch-specific recovery mean it no longer has that single shape, and a loop is clearer. Pythonic code reduces the state a reader must hold at once; it does not merely reduce line count.