Any and All

Here's an ipython notebook with the code for this post, and the ipython notebook source.

any and all are useful Python functions. Given an iterable (like a list, a generator, an str, etc.), these functions check if any or all of the values are True (or "truthy"). Here is a simple example where we are checking the values of the letters in the word "python":

any(l == 't' for l in 'python') # Returns True. Same as: 't' in 'python'
all(l == 't' for l in 'python') # Returns False. Not all of the letters are 't'.

This seems like a simple function call, but things are slightly more complicated than they look.

First, a generator is constructed from the expression l == 't' for l in 'python'. What is a generator? A generator is an object which is both an iterator (has the next method) and is also iterable (has the __iter__ method which returns an iterator). Strings themselves are iterable, which is how the for l in 'python' part works.

You can define a generator through a generator expression:

g = (l == 't' for l in 'python')

Note that a generator expression is similar to a list comprehension, except uses parentheses instead of brackets. A key difference is that while a list comprehension immediately computes all of the values and stores them in a list in memory, a generator expression generates its values lazily on demand.

To show that g is an iterator, we can get the first few values by hand:

print g.next() # False. 'p' is not equal to 't'
print g.next() # False. 'y' is not equal to 't'
print g.next() # True. 't' is equal to 't'

Again, these True/False responses are lazily computed by the generator only upon demand. You can also use g in a loop, since it's iterable. Under the hood, the for loop calls the __iter__ function of g:

g = (l == 't' for l in 'python')
for value in g:
    print value

Functions that accept iterable objects, like any and all, will accept a generator expression:

g = (l == 't' for l in 'python')
any(g)
any( (l == 't' for l in 'python') ) # same thing

Finally, python lets us avoid the "double parentheses" when we pass a generator expression to a function call:

any(l == 'l' for l in 'hello') # same thing as any((l == 't' for l in 'python'))

So that's how the function call itself works. How do any and all work with the iterable it is given?

Let's demonstrate this with a simple example:

def t():
    print 'In True!'
    return True

def f():
    print 'In False!'
    return False

# Store functions to be called in a list
funcs = [t, f, f, f, t]

def test_any():
    # Pass a generator expression with function calls to any
    print any(func() for func in funcs)

def test_all():
    # Pass a generator expression with function calls to all
    print all(func() for func in funcs)

test_any() # Calls t() once and stops.
test_all() # Calls t(), then f(), then stops

In this example, any stopped after the first True value it sees, and all stopped after the first False value. This makes sense - both any and all can stop iterating over their argument once they've figured out their return value.

The more you know!

Go Top
comments powered by Disqus