How can you check whether all items match a condition in Python? And how can you check whether any item matches a condition?
You might think to do something like this:
all_good = True
for item in iterable:
if not condition(item):
all_good = False
break
Or something like this:
any_bad = False
for item in iterable:
if condition(item):
any_bad = True
break
But there are two built-in Python functions that can help make those code blocks more descriptive and more readable.
Let's talk about using Python's any
and all
functions to check whether all iterable items match a condition.
This function accepts a list (or iterable) of numbers and checks whether all the numbers are between 0
and 5
(inclusive):
def ratings_valid(ratings):
for rating in ratings:
if rating < 0 or rating > 5:
return False
return True
This code:
for
loop)False
as soon as a negative number or a number greater than 5 is found (using an if
statement to check that condition)True
if all numbers are between 0
and 5
Note that this function returns as soon as it finds an invalid number, so it only iterates all the way through the number range if all the numbers are valid.
any
and all
functions in PythonLet's first look at a refactoring of our loop that checks a condition for each item and then discuss what we did, why we did it, and how we did it.
Spoiler alert! ⚠️ We're about to look at the code we'll eventually end up with and then we'll step back and refactor our way to that code.
We started with this:
def ratings_valid(ratings):
for rating in ratings:
if rating < 0 or rating > 5:
return False
return True
We're going to end up either this:
def ratings_valid(ratings):
return not any(
rating < 0 or rating > 5
for rating in ratings
)
Or we'll end up with this (which is even more readable):
def ratings_valid(ratings):
return all(
0 <= rating <= 5
for rating in ratings
)
Okay, let's break this code down to see how it works.
First we need to talk about the any
and all
functions.
Then we'll talk about why any
and all
tend to be used with generator expressions, how to choose between them.
Finally, we'll wrap up with a cheat sheet.
any
functionPython has a built-in any
function that returns True
if any items are truthy
>>> any([True, False, False, True, False])
True
>>> any([False, False, False, False, False])
False
>>> any(['', '', ''])
False
>>> any(['', 'Python', ''])
True
Truthy typically means non-empty or non-zero, but for our purposes you can think of it pretty much the same as True
.
Python's any
is equivalent to this:
def any(iterable):
for element in iterable:
if element:
return True
return False
Notice the similarity between any
and our ratings_valid
function: their structure is very similar, but not quite the same.
The any
function checks for the truthiness of each item in a given iterable, but we need something a little more than that: we need to check a condition on each element.
Specifically, we need to check whether a number is between 0
and 5
.
all
functionPython also has a built-in all
function that returns True
if all items are truthy.
>>> all([True, False, True, True, False])
False
>>> all([True, True, True, True, True])
True
>>> all(['Python', 'is', 'neat'])
True
>>> all(['', 'Python', 'is', 'neat'])
False
Python's all
is equivalent to this:
def all(iterable):
for element in iterable:
if not element:
return False
return True
The any
and all
functions are two sides of the same coin:
any
returns True
as soon as it finds a matchall
returns False
as soon as it finds a non-matchThe any
and all
functions accept an iterable and check the truthiness of each item in that iterable.
These functions are typically used with iterables of boolean values.
We could re-implement our ratings_valid
function by building up a list of boolean values and then passing those values to any
:
def ratings_valid(ratings):
rating_is_invalid = []
for rating in ratings:
rating_is_invalid.append(rating < 0 or rating > 5)
return not any(rating_is_invalid)
Or we could copy-paste that for
loop into a list comprehension:
def ratings_valid(ratings):
return not any([
rating < 0 or rating > 5
for rating in ratings
])
That code is shorter, but there's a problem: we're building up a new list just to loop over it once! This is less efficient than our original approach!
Our original approach only looped all the way through the list if all ratings were valid (see the return
within the for
loop).
def ratings_valid(ratings):
for rating in ratings:
if rating < 0 or rating > 5:
return False
return True
Let's fix this inefficiency by turning our list comprehension into a generator expression.
A generator expression is like a list comprehension, but instead of making a list it makes a generator object.
def ratings_valid(ratings):
return not any((
rating < 0 or rating > 5
for rating in ratings
))
We can actually even drop the double parentheses and still have valid Python code:
def ratings_valid(ratings):
return not any(
rating < 0 or rating > 5
for rating in ratings
)
It's so common to pass a generator expression directly into a function call that the Python core developers added this feature just for this use case.
Generators are lazy iterables: they don't compute the items they contain until you loop over them.
Instead they compute their values item-by-item.
This allows us to stop computing as soon as the any
function finds a truthy value!
There's one more thing we can do to improve the readability of this code: we could switch to using Python's all
function.
any
and all
We started with a for
loop, an if
statement, a return
statement within our loop, and a return
statement at the end of our loop:
def ratings_valid(ratings):
for rating in ratings:
if rating < 0 or rating > 5:
return False
return True
We turned all that into a generator expression passed to the any
function:
def ratings_valid(ratings):
return not any(
rating < 0 or rating > 5
for rating in ratings
)
Note that we negated the final result (see the not
in return not any...
)
In English, this reads "there is not any rating outside the target range".
Using the all
function we're able to lose that negation:
def ratings_valid(ratings):
return all(
not (rating < 0 or rating > 5)
for rating in ratings
)
Or if we rely on Python's chained comparisons, we could write:
def ratings_valid(ratings):
return all(
0 <= rating <= 5
for rating in ratings
)
In English, this reads "all the ratings are within the target range".
These two statements are equivalent in Python:
>>> all_match = all(condition(x) for x in iterable)
>>> all_match = not any(not condition(x) for x in iterable)
And these two statements are equivalent as well:
>>> any_match = any(condition(x) for x in iterable)
>>> any_match = not all(not condition(x) for x in iterable)
Typically one of your any
or all
expressions will read more clearly than the other one.
In our case, it's the all
expression that reads more clearly.
any
and all
in an if
statementI've mostly been showing any
and all
used in a return
statement, but that's actually not its most readable use.
If you don't find the behavior of these functions to be intuitive, you might find them easier to understand when used in an if
statement.
For example let's say we have an is_valid_rating
function like this:
def is_valid_rating(rating):
return 0 <= rating <= 5
We could use that function to write an if
statement like this:
if all(is_valid_rating(r) for r in ratings):
print("All the ratings are valid")
else:
print("Not all ratings are valid")
In English I would describe that code as checking whether "all ratings are valid" and then printing based on the result.
Compare that code to this:
all_valid = True
for r in ratings:
if not is_valid_rating(r):
all_valid = False
break
if all_valid:
print("All the ratings are valid")
else:
print("Not all ratings are valid")
In English this reads as "loop over all ratings and set all_valid
to False
if an invalid rating is found or set it to True
otherwise" and then print based on the value of all_valid
.
This for
loop doesn't translate into English nearly as easily as that if
statement with all
do!
I very much prefer this first approach of using a generator expression with all
because I find it more descriptive than the equivalent for
loop.
Python's any
and all
functions are for checking a condition for every item in a list (or any other iterable).
While any
and all
are handy, sometimes there's an even simpler condition checking tool available.
For example this loop:
>>> numbers = [2, 1, 3, 4, 7, 11]
>>> has_five = False
>>> for n in numbers:
... if n == 5:
... has_five = True
... break
...
Could be written like this:
>>> numbers = [2, 1, 3, 4, 7, 11]
>>> has_five = any(n == 5 for n in numbers)
But this could be simplified even further.
We're trying to check whether a given number is contained in the numbers
list.
The in
operator is made exactly for this purpose:
>>> numbers = [2, 1, 3, 4, 7, 11]
>>> has_five = 5 in numbers
Python's in
operator is for containment checks.
Anytime you need to ask whether a particular item is contained in a given iterable, the in
operator is the tool to reach for.
Additionally, when you find yourself searching for a matching item in an iterable, in general you may want to question whether you should use a dictionary instead.
any
and all
Here's a quick cheat sheet for you.
This code uses a break
statement because we're not returning from a function: using break
stops our loop early just as return
does.
any
and all
functionsPython's any
and all
functions were made for use with generator expressions (discussed here & here) and they're rarely used without them.
The next time you need to check whether all items match a condition, try Python's any
or all
functions along with a generator expression.
For more of my recommendations on how to use Python's built-in functions, see Built-in functions in Python.
Need to fill-in gaps in your Python skills?
Sign up for my Python newsletter where I share one of my favorite Python tips every week.
Need to fill-in gaps in your Python skills? I send weekly emails designed to do just that.