Software by Steve

Oct 09, 2014

Python List and Dict Comprehensions for PHP Developers

When moving from PHP to Python, your experience with many of the basic language features and constructs are easily carried over. Sure, the syntax is different, but not radically so. Things like loop constructs, basic data structures and even OOP don't take long to feel familiar. One area where this is not true, however, is Python's list and dict comprehensions. PHP doesn't have an equivalent here, and in my opinion, this is an area where Python really shines in terms of functionality, ease of use and succinctness.

In this post, I'll explain what list and dict comprehensions are, and show side-by-side examples of PHP and Python to demonstrate how and why you'd use them.

First, the definition from the official docs:

"List comprehensions provide a concise way to create lists. Common applications are to make new lists where each element is the result of some operations applied to each member of another sequence or iterable, or to create a subsequence of those elements that satisfy a certain condition."

Let's begin with a simple example. We'll use an array of associative arrays in PHP and a list of dicts in Python to represent (some of) Pink Floyd's discography.

PHP:

<?php
$albums = [['name' => 'The Piper at the Gates of Dawn', 'year' => 1967],
            ['name' => 'Atom Heart Mother', 'year' => 1970],
            ['name' => 'The Dark Side of the Moon', 'year' => 1973],
            ['name' => 'Animals', 'year' => 1977],
            ['name' => 'The Wall', 'year' => 1979]];

Python:

albums = [{'name': 'The Piper at the Gates of Dawn', 'year': 1967},
            {'name': 'Atom Heart Mother', 'year': 1970},
            {'name': 'The Dark Side of the Moon', 'year': 1973},
            {'name': 'Animals', 'year': 1977},
            {'name': 'The Wall', 'year': 1979}]

The first thing we want to do is grab a list of just the album names:

PHP:

<?php
$album_names = [];
foreach ($albums as $album) {
    $album_names[] = $album['name'];
}
// or, preferably using array_map()...
$album_names = array_map(function($album) {
    return $album['name'];
}, $albums);

Every PHP developer should be intimately familiar with the above. For developers new to Python, it's natural to do something like this:

Python:

album_names = []
for album in albums:
    album_names.append(album['name'])

But, there's an easier (and arguably better) way to do this, using a list comprehension:

Python:

album_names = [album['name'] for album in albums]

Try this out in your REPL of choice and you'll end up with:

['The Piper at the Gates of Dawn', 'Atom Heart Mother', 'The Dark Side of the Moon', 'Animals', 'The Wall']

In Python, a list comprehension takes the following form:

[(expression) (for clause) (zero or more if statements)]

Say we wanted to return a list of lists, where each inner list contains two elements: the year first and then the album name:

PHP:

<?php
$by_year_name = array_map(function($album) {
    return [$album['year'], $album['name']];
}, $albums);

Python:

by_year_name = [[album['year'], album['name']] for album in albums]
# [[1967, 'The Piper at the Gates of Dawn'], [1970, 'Atom Heart Mother'], [1973, 'The Dark Side of the Moon'], [1977, 'Animals'], [1979, 'The Wall']]

Only want albums released after 1970?

PHP:

<?php
$filtered = array_map(function($album) {
    if ($album['year'] > 1970) {
        return [$album['year'], $album['name']];
    }
}, $albums);

Python:

filtered = [[album['year'], album['name']] for album in albums if album['year'] > 1970]
# [[1973, 'The Dark Side of the Moon'], [1977, 'Animals'], [1979, 'The Wall']]

Don't like 'The Wall'?

PHP:

<?php
$filtered = array_map(function($album) {
    if ($album['year'] > 1970 && $album['name'] != 'The Wall') {
        return [$album['year'], $album['name']];
    }
}, $albums);

Python:

# We'll move the if statements to a new line for the sake of readability
filtered = [[album['year'], album['name']] for album in albums 
            if album['year'] > 1970 and album['name'] != 'The Wall']
# [[1973, 'The Dark Side of the Moon'], [1977, 'Animals']]

By now you should see how much more terse the Python examples are compared to PHP. This continues to hold true as your logic becomes more complex.

So, what are dict comprehensions? Turns out they're like list comprehensions, but create dicts instead (shocking, I know.)

Let's index the albums by year, but only for those that were released before 1973:

PHP:

<?php
$indexed = [];
foreach ($albums as $album) {
    if ($album['year'] < 1973) {
        $indexed[$album['year']] = $album['name'];
    }
}

Python:

indexed = {album['year']: album['name'] for album in albums if album['year'] < 1973}
# {1970: 'Atom Heart Mother', 1967: 'The Piper at the Gates of Dawn'}

The syntax may look different, but it still follows the form of:

[(expression) (for clause) (zero or more if statements)]

You can work with dicts in list comprehensions, and vice versa. Let's return the original list of dicts, but with an uppercased version of the album names:

uppercased = [{'name': album['name'].upper(), 'year': album['year']} for album in albums]
# [{'name': 'THE PIPER AT THE GATES OF DAWN', 'year': 1967}, {'name': 'ATOM HEART MOTHER', 'year': 1970}, {'name': 'THE DARK SIDE OF THE MOON', 'year': 1973}, {'name': 'ANIMALS', 'year': 1977}, {'name': 'THE WALL', 'year': 1979}]

An important thing to remember is the symbols on the outside of the comprehension determine what type of data structure you'll get back. Square braces are used for list comprehensions, and curly brackets are used for dict comprehensions.

Let's finish with one more example: returning a generator:

# Notice how we use parens instead of square braces
album_gen = (album for album in albums if album['year'] > 1970)
# <generator object <genexpr> at 0x109d4ff50>

There's still alot more you can do with list and dict comprehensions that's outside the scope of this post. I'll leave it as an exercise for the reader to play around (hint: try nesting comprehensions or figuring out when you would want to use map or filter instead of a comprehension.)

List and dict comprehensions are a terrific feature of Python that I wish PHP had. They make working with data collections much easier. Add them to your arsenal and you'll be glad you did!

Tagged:
Click to read and post comments