Get one quick Python tip each week Weekly Python advice: one quick Python tip every week

Using "any" and "all" in Python

Share
Tags

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:

for item in iterable:
if condition(item):
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.

Checking a condition for all items

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:

1. Loops over all given numbers (using a for loop)
2. Returns False as soon as a negative number or a number greater than 5 is found (using an if statement to check that condition)
3. Returns 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.

Using the any and all functions in Python

Let'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.

Python's any function

Python 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.

Python's all function

Python 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 match
• all returns False as soon as it finds a non-match

Let's try using a list comprehension

The 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.

Using 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.

Choosing between 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.

Using any and all in an if statement

I'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.

You might just need a containment check

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.

Cheat sheet: Python's 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.

Check whether all items match a condition with the any and all functions

Python'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.

A Python Tip Every Week

Need to fill-in gaps in your Python skills? I send weekly emails designed to do just that.