Originally popularised by the functional language Haskell, list comprehensions give you a different way to code in Python that allows you to focus on the data you're transforming, rather than the functions you use.

Anything you can do with list comprehensions you can achieve using the built-in functions map and filter, but list comprehensions provide you with a simpler to use and more readable syntax. In this article I'll show you the power of list comprehensions by going through some simple examples.

When you're specifying a list in Python, you enumerate your items and surround them with square brackets like so:

>>> wordlist = ['HELLO', 'World', 'how', 'aRe', 'YOU?']

When you use list comprehensions you do the same thing, except that instead of enumerating the contents, you describe the contents as a transformation of another list. Let's take an example: we wanted to take the list of words and make them lowercase. Normally we would use the following:

>>> l = []
>>> for word in wordlist:
        l.append(word.lower())
>>> l
['hello', 'world.', 'how', 'are', 'you?']

But that's too long, and it's slow, since Python has to implicitly loop whenever we use a for statement. If we were used to functional programming we could write instead:

>>> import string
>>> map(string.lower,wordlist)
['hello', 'world.', 'how', 'are', 'you?']

That's an improvement, but it's a little cryptic. By using a list comprehension instead we could write:

>>> [word.lower() for word in wordlist]
['hello', 'world.', 'how', 'are', 'you?']

The second version isn't any shorter, but it can be clearer in many circumstances. You'll have to decide for yourself which approach suits your circumstances.

List comprehensions can also be used to completely replace the built-in filter function, for example if you wanted only the words in the list that were already lowercase you could write:

>>> [word for word in wordlist if world.islower()]
['how']

map and filter are the cornerstones of functional programming, using list comprehensions you can use either or both in an intuitive way. If we wanted to combine both map and filter in one expression it is as simple as:

>>> [word.lower() for word in wordlist if not world.islower()]
['hello', 'world.', 'are', 'you?']

You can nest list comprehensions either in the first section like so:

>>> vowels = ['a','A','e','E','i','I','o','O','u','U']]
>>> [[letter for letter in word if letter not in vowels] for word in wordlist]
[['H', 'L', 'L'], ['W', 'r', 'l', 'd'], ['h', 'w'], ['R'], ['y', '?']]

Or in the last section, allowing you to chain multiple tranformations together:

>>> [a.lower() for a in [b[i] for b in x for i in range(len(b))]]
['h', 'e', 'l', 'l', 'o', 'w', 'o', 'r', 'l', 'd', 'h', 'o', 'w', 'a', 'r', 'e', 'y', 'o', 'u', '?']

As you can see, when you start chaining list comprehensions together things get complicated. When you have more than one list comprehension in an expression it's a good idea to break things up on to multiple lines. You don't always need to nest list comprehensions though, if all you're doing is iterating over multiple lists you can include them in the same list comprehension.

In the following example we want to generate all permutations of guests to a dinner party, we can do it with one simple list comprehension:

>>> guests = ['Chris', 'Brendan', 'Jimmy', 'Mel', 'Mike', 'Jess']
>>> [(seat1, seat2) for seat1 in guests for seat2 in guests if seat1 != seat2]
[('Chris', 'Brendan'), ('Chris', 'Jimmy'), ('Chris', 'Mel'), ('Chris', 'Mike'), ('Chris', 'Jess'), ('Brendan', 'Chris'), ('Brendan', 'Jimmy'), ('Brendan', 'Mel'), ('Brendan', 'Mike'), ('Brendan', 'Jess'), ('Jimmy', 'Chris'), ('Jimmy', 'Brendan'), ('Jimmy', 'Mel'), ('Jimmy', 'Mike'), ('Jimmy', 'Jess'), ('Mel', 'Chris'), ('Mel', 'Brendan'), ('Mel', 'Jimmy'), ('Mel', 'Mike'), ('Mel', 'Jess'), ('Mike', 'Chris'), ('Mike', 'Brendan'), ('Mike', 'Jimmy'), ('Mike', 'Mel'), ('Mike', 'Jess'), ('Jess', 'Chris'), ('Jess', 'Brendan'), ('Jess', 'Jimmy'), ('Jess', 'Mel'), ('Jess', 'Mike')]

It's not going to replace everything in your programs, but add list comprehensions to your Python toolbox and you'll soon see that they can make your programs smaller, clearer and faster than they were previously, and since they concentrate on data rather than process, they're easy to write -- even when what you need to do is quite complicated.

Do you need help with Python? Gain advice from Builder AU forums

Comments

1

theearthboundkid - 04/07/07

Cross post from reddit.

List comprehensions are wonderful, but some of these examples are 
less clear than they could be.

>>> [a.lower() for a in [b[i] for b in x for i in range(len(b))]]
['h', 'e', 'l', 'l', 'o', 'w', 'o', 'r', 'l', 'd', 'h', 'o', 'w', 'a', 'r', 'e', 'y', 'o', 'u', '?']

would be clearer as

>>> [char.lower() for word in mylist for char in word]

9 times out of 10, you don't need to use range or len for your list
comprehensions.

One cool trick is to use generators instead of list comprehensions.
They're almost the same, but generators use parentheses instead of
brackets. The advantage of generators is that you can pass them to
functions or loops without wasting time building up a list object that
will then be GC'd one line down. Here's something cool you can do
with a generator:

>>> any(mytest(i) for i in mylist)

This will tell you if any member of your list meets your test criteria.
Since it uses a generator instead of a list comprehension, it stops
testing as soon as it gets back an item that tests true. A list
comprehension would go all the way through the list even if it
matched on the first test. Here's another good one:

>>> mycounter = sum(1 for i in mylist if cond(i))

This will tell you the number of items in the list that match your
condition. It's the same as:

>>> mycounter = 0
>>> for i in mylist:
... if cond(i):
... mycounter = 1
...

OK, finally, here are some insane list comprehensions from the
Python cookbook:

# sum
nl = [2, 3, 6, 18]
[j for j in [0] for i in nl for j in [j i]][-1]

# product
nl = [2, 3, 6, 18]
[j for j in [1] for i in nl for j in [j * i]][-1]

# factorial
fac = 6
[j for j in [1] for i in xrange(2, fac 1) for j in [j*i]][-1]

While you should never use these in production code (too cryptic!),
they are really insanely cool.

» Report offensive content

Leave a comment

You must read and type the 6 chars within 0..9 and A..F

* indicates mandatory fields.

1

theearthboundkid - 07/04/07

Cross post from reddit.List comprehensions are wonderful, but some of these examples are less clear than they could be. >>> [a.lower() ... more

Log in


Sign up | Forgot your password?

  • Staff Aussies to pay more for Win 7

    If you are looking to make some money in these troubled times, perhaps importing copies of Windows 7 could be for you. Read more »

    -- posted by Staff

  • Staff Firefox: Greens want it, 3.5rc2 not up to par

    This week's roundup looks at the situation surrounding a campaign to change Outlook HTML renderer, a Greens MP wants to install Firefox but is restricted and all the photos from the iPhone 3GS launch. Read more »

    -- posted by Staff

  • Chris Duckett Microsoft misses the Outlook point

    Ask designers which mail program is the bane of their existence, and you'll find that Outlook tops the list. The reason why the most popular email reader is also the most painful is simple: it uses Word to render HTML emails. Read more »

    -- posted by Chris Duckett

What's on?