While comprehensions are quite popular among Python developers, I think they're still underused.
There are at least three good reasons why we should use comprehensions in Python. Let's see what they are.
First: They isolate our list creationPermalink
They make it harder for others to interfere with our list creation.
When we use a for loop, other people can come later and add things to our for loop.
Say we start with something really simple. We have a list of players. We want to export good players daily - those with a score of more than 100 for a given day:
good_players = []
for player in players:
if player.today_score > 100:
good_players.append(player)
export_good(good_players)
Ok, that looks fine. What's the issue, then?
It is too easy for others to come later and add their own things to this for loop that have nothing to do with our original task.
So in a few weeks, our loop ends up looking like this:
good_players = []
teams = []
emails = []
daily_stats = load_stats()
for player in players:
if player.has_played_today:
player.update_stats(daily_stats)
player.save()
if player.today_score > 100:
email.append(player.email)
add_free_credit_to(player)
if player.team_name not in teams:
teams.append(player.team_name)
good_players.append(player)
send_emails(emails)
process_credits()
notify_teams(teams)
export_good(good_players)
A lot is going on there. It takes much more time to understand what is happening.
Our original feature (exporting good players) is now buried under a bunch of other things. So it is now easier to break.
There is a bug in the code above. We repeatedly give free credits to all players; that is not right. And this is in production already!
We want to give free credit only to new players who joined today and managed to earn a score of 100 or more.
So someone comes in a rush, looks at the code and quickly updates the if
statement like this:
if player.today_score > 100 and player.join_date == today():
email.append(player.email)
add_free_credit_to(player)
if player.team_name not in teams:
teams.append(player.team_name)
good_players.append(player)
And now our original code is broken! We're not exporting all good players anymore. Only players that also joined today, which is not what we want for our export.
And it all happened because it was too easy for others to add to our simple loop. That made it harder to understand, and so the person fixing the bug in a rush missed the fact that the good_players
list is also part of that if
because it was hidden after another if.
Instead, with a list comprehension, we could do this:
good_players = [player for player in players if player.score > 100]
export_good(good_players)
It isolates our list building into one expression. It makes it harder to break because others have no easy way to interfere with our export of good players.
So instead of fiddling with our list comprehension, they would go to do their own thing. Others can still do for loops if needed, but our code will be independent of those and work as expected.
Second: They’re very concise and clearPermalink
Clarity is essential because code is written once and read many times.
We don't want to unnecessarily read pages and pages of code if it could be written more concisely. I intentionally said concise, which is not the same as short.
For a programmer who knows comprehensions, it is faster to understand a comprehension than a for loop.
For loop might do many things, list comprehension always does one thing - creates a new list from another iterable.
Concise code is easier to read and understand and thus easier and cheaper to maintain.
Third: They are faster than for loopsPermalink
Comprehensions are faster than for loops if we use them where they should be used.
Explaining in detail why that is the case is outside the scope of this article but in short, comprehensions are optimised and executed at the C level rather than at the Python level.
Below is simple measuring.
We create a list of numbers from 0 to 100. Then we make a copy of that list. First with a for loop and then with a comprehension.
Comprehension is more than twice faster:
projects % python3 -m timeit -s 'scores=list(range(100))' 'my_scores =[]' 'for n in scores: my_scores.append(n)'
50000 loops, best of 5: 4.65 usec per loop
projects % python3 -m timeit -s 'scores=list(range(100))' 'my_scores = [n for n in scores]'
200000 loops, best of 5: 1.97 usec per loop
SummaryPermalink
Comprehensions make our code:
- more robust (harder to break).
- more concise and clearer.
- faster.
Happy coding!
You might also like
Better Python apps in AWS with stelvio.dev
Deep dives and quick insights into cloud architecture.