Conditions inside a comprehension come in two different forms, and confusing them is the source of most comprehension bugs. An if clause after the for filters the items. A conditional expression before the for transforms them. Read carefully, the two never overlap.
How do you filter with an if clause?
The if clause goes after the for clause. Items that fail the test are dropped entirely, and the output expression is never evaluated for them.
values = [-3, 5, -1, 8, 0]
positives = [v for v in values if v > 0]
print(positives)
squares = [v * v for v in values if v > 0]
print(squares)
That order is worth stating plainly: the filter runs first, and the expression runs only on what survives. The second line computes v * v for three values and never touches the negative ones, which is also why it cannot raise an error on them.
The test can be any expression that evaluates to true or false, including a function call. That is how you filter on something you have to work out rather than read directly.
Filtering is not a separate step here — it is part of the same expression, so there is no intermediate list of survivors sitting in memory between the filter and the transformation.
def is_even(n):
return n % 2 == 0
numbers = [1, 2, 3, 4, 5, 6]
print([n for n in numbers if is_even(n)])
print([n for n in numbers if n > 2 and is_even(n)])
What is the difference between an if clause and a conditional expression?
A conditional expression has the form value_if_true if condition else value_if_false. It sits at the front, where the output expression goes, and it chooses between two values rather than removing items.
values = [-3, 5, -1, 8, 0]
absolute = [v if v > 0 else -v for v in values]
print(absolute)
filtered = [-v for v in values if v > 0]
print(filtered)
Look at the difference in the outputs. The conditional expression keeps every item and changes negative ones to positive, so the list has five elements. The if clause keeps only the positive items and then negates them, so the list has two. Same words, different slots, completely different results.
The conditional expression is not special to comprehensions — it is an ordinary expression that you can use anywhere a value is expected. That makes it useful well beyond this syntax, and it makes x if condition else y a good habit to recognise wherever you see it.
Two mistakes follow from this. Putting the condition in the wrong slot changes the length of your result, which is usually the symptom you notice first. And writing if where else should be — v if v > 0 else with nothing after it — is a syntax error, because a conditional expression needs both branches.
source = "values = [v if v > 0 else for v in values]"
try:
compile(source, "<demo>", "exec")
except SyntaxError as exc:
print("SyntaxError:", exc.msg)
How do you combine multiple conditions?
Join them with and and or inside the single if clause. Every condition has to be true for the item to survive when you use and.
numbers = [1, 2, 3, 4, 5, 6, 7, 8]
print([n for n in numbers if n % 2 == 0 and n > 3])
print([n for n in numbers if n in (2, 4, 8)])
print([n for n in numbers if n < 3 or n > 6])
Use parentheses when mixing and and or, because and binds more tightly than or. That is the same precedence rule you already use everywhere else in Python, but inside a comprehension it is easy to miss because the surrounding brackets belong to the list, not to the condition.
A bare if x is a filter on truthiness, which is how you drop empty strings, zeros and empty containers in one go. It is short, and it can hide what you meant, so use it when truthiness is really what you mean.
mixed = ["hello", "", "python", None, "code"]
print([text for text in mixed if text])
print([text for text in mixed if text is not None])
You can also write more than one if clause, and the effect is the same as joining them with and. The single-clause form reads better in almost every case.
When should you use any() and all() instead of building a list?
When the question is a yes-or-no one. If you only need to know whether at least one item matches, any() stops at the first match instead of building a whole list to inspect it afterwards.
numbers = [1, 2, 3, 4]
print(any(n > 3 for n in numbers))
print(all(n > 0 for n in numbers))
print([n for n in numbers if n > 3])
The parentheses hold a generator expression, which produces values one at a time rather than a list up front. That laziness is what lets any() and all() stop early, and it also keeps memory flat when you are scanning a large collection.
One edge case catches people out: all() on an empty sequence returns True, because there is no item that fails the test. any() on an empty sequence returns False. If an empty collection should be treated as a failure, check the length explicitly.
print(all([]))
print(any([]))
values = [1, 2, 3]
print(all(v > 0 for v in values))
The same generator expression works with filter() and map(), but the comprehension is usually easier to read. Reach for filter() when you are combining it with other functions in a chain, and for the comprehension when readability is what matters.
What can the walrus operator do inside a comprehension?
Python 3.8 added the assignment expression :=, which binds a name and hands back the value in one go. Inside a comprehension it lets you compute something once and use it in both the test and the output expression.
data = [1, -2, 3]
doubled = [y for x in data if (y := x * 2) > 0]
print(doubled)
Without the walrus you would either compute x * 2 twice or reach for a helper function. The walrus is a genuine shortcut here, but it comes with two restrictions worth knowing before you meet them in a traceback.
try:
compile("[y for x in (y := [1, 2, 3])]", "<demo>", "exec")
except SyntaxError as exc:
print("SyntaxError:", exc.msg)
try:
compile("class C:\n y = [x for x in (1, 2) if (y := x)]\n", "<demo>", "exec")
except SyntaxError as exc:
print("SyntaxError:", exc.msg)
First, an assignment expression cannot appear in the iterable part of a comprehension, because that part is evaluated before the comprehension's scope exists. Second, it cannot be used inside a comprehension in a class body, because the name would leak into the class namespace in a confusing way. Both are compile-time errors, so you find out immediately rather than at runtime.
One more consequence to keep in mind: the name a walrus binds lives in the enclosing scope, not inside the comprehension. After the comprehension finishes, the name is still there.
Filtering recipes
| Goal | Code |
|---|---|
| Keep matching items | [x for x in items if test(x)] |
| Transform, keep everything | [f(x) if cond else g(x) for x in items] |
| Transform and filter | [f(x) for x in items if test(x)] |
| Two tests | [x for x in items if a(x) and b(x)] |
| At least one match | any(test(x) for x in items) |
| Every item matches | all(test(x) for x in items) |
| Compute once, use twice | [y for x in items if (y := f(x))] |
Try it yourself: take a list of numbers, keep only the even ones, and square them — in one comprehension. Then write the same thing with a conditional expression and notice that the length changes. Next, check whether any number is negative and whether all are under 100, using any and all rather than a list. Finally, use a walrus to compute each number's absolute value once and filter on it, then confirm by printing the bound name afterwards that it survived outside the comprehension.