Skip to main content

Comprehensions

As you code and program with Python you'll often find yourself working with collections of data- lists of numbers, sets of unique items, dictionaries of key-value pairs, and even generating sequences on the fly with generators and you'll need to transform this data somehow.

While conditional statements such as if statements and for loops are the typical tools used for such tasks, Python provides an even more concise and efficient approach- comprehensions- which allow you to create new list/set/dictionary/generator objects by applying an operation and/or a condition to the given iterable.

Comprehensions follow this basic format-

new_iterable=[expression for item in iterable]
  • new_iterable is the new iterable object created by the comprehension.
  • [] is the brackets used to denote which comprehension you're working with. So, List comprehension uses [], dictionary and set comprehensions use , and generator comprehensions use ()
  • expression is the The expression applied to each item.
  • for is a Python keyword that begins the loop over each item in the iterable.
  • item is a variable that represents each item being iterated upon.
  • iterable is the sequence object that is being processed by the comprehension.

We can also add in an if statement to comprehensions as well like this-

new_iterable=[expression for item in iterable if condition]

The if statement here works exactly like how it would normally- it applies a conditional if statement to comprehension, allowing us to further control what it does.

The beauty of comprehensions lies in their ability to replace multi-line conditional statements with a single, clear line of code, making your code more Pythonic and easier to format and read. It may take some time to get used to its syntax, but once you do you'll undoubtedly find them very valuable.

Here we'll explore the four types of comprehensions- list, set, dictionary, and generator expressions- and see how each can be used to elegantly handle common data manipulation tasks. It tends to be easier to understand comprehensions when we see them compared to their control flow equivalents, so we'll explore that as well.

List Comprehension

A list comprehension is defined within square brackets [], following this basic format:

[expression for item in iterable]

You can also integrate a conditional statement into a list comprehension to filter items based on a condition:

[expression for item in iterable if condition]

Let's go over some practical examples to showcase this in action.

Example 1

Suppose we want to iterate over a string and return its characters one by one. Here's how we can do it with a traditional for loop:

string = "Hello!"
characters = []
for char in string:
characters.append(char)
a
print(characters) #Outputs: ['H', 'e', 'l', 'l', 'o', '!']

Compare that to its list comprehension equivalent:

string = "Hello!"
characters = [char for char in string]
print(characters) #Outputs: ['H', 'e', 'l', 'l', 'o', '!']

We can easily encapsulate the four lines of code into a single line with list comprehension, making our code easier to follow.

Example 2

Suppose we want to extract even numbers from a range. Here's how we can do it with a traditional for loop:

even_numbers = []
for i in range(11):
if i % 2 == 0:
even_numbers.append(i)

print(even_numbers) #Outputs: [0, 2, 4, 6, 8, 10]

Compare that with its list comprehension equivalent:

even_numbers = [i for i in range(11) if i % 2 == 0]
print(even_numbers) #Outputs: [0, 2, 4, 6, 8, 10]

Once again- much easier to simpler to follow.

Set Comprehension

A set comprehension is defined within curly braces {}, following this basic format:

{expression for item in iterable}

You can incorporate a conditional statement into a set comprehension to filter items based on a condition:

{expression for item in iterable if condition}

Let's explore some practical examples to demonstrate this in action.

Example 1

Suppose you have a list of names where some names appear more than once, and you want to ensure each name is capitalized and unique for a registration list.

Here's how we can do it with a traditional for loop:

names = ["alice", "bob", "Alice", "Bob", "charlie", "Charlie"]

unique_names = set()

for name in names:
unique_names.add(name.capitalize())

print(unique_names) # Outputs: {'Alice', 'Bob', 'Charlie'}

Here's how we can accomplish this with a set comprehension:

names = ["alice", "bob", "Alice", "Bob", "charlie", "Charlie"]

unique_names = {name.capitalize() for name in names}

print(unique_names) # Outputs: {'Alice', 'Bob', 'Charlie'}

Example 2

Imagine you have a list of integers representing account balances, and you're interested in quickly identifying which accounts have a positive balance, ignoring any duplicates.

Here's how we can do it with a traditional for loop:


balances = [100, -50, 200, 300, -150, 100, 200]
positive_balances_list = []

for balance in balances:
if balance > 0:
positive_balances_list.append(balance)

positive_balances_set = set(positive_balances_list)

print(positive_balances_set) # Outputs {200, 100, 300}

Here's how we can accomplish this with a set comprehension:

balances = [100, -50, 200, 300, -150, 100, 200]
positive_balances = {balance for balance in balances if balance > 0}

print(positive_balances) # Outputs {200, 100, 300}

Dictionary Comprehension

A dictionary comprehension is defined within curly brackets {}, following this basic format:

{key_expression: value_expression for item in iterable}

You can also add a conditional statement into a dictionary comprehension to filter items based on a condition:

{key_expression: value_expression for item in iterable if condition}

Let's explore some practical examples to demonstrate this in action.

Example 1

Suppose we want to create a dictionary that maps each character in a string to its ASCII value.

Here's how we could achieve this with a traditional for loop:

string = "hello"
char_to_ascii = {}
for char in string:
char_to_ascii[char] = ord(char)

print(char_to_ascii) #Outputs: {'h': 104, 'e': 101, 'l': 108, 'o': 111}

Here's how we can accomplish this with a dictionary comprehension:


string = "hello"
char_to_ascii = {char: ord(char) for char in string}
print(char_to_ascii) #Outputs: {'h': 104, 'e': 101, 'l': 108, 'o': 111}

Example 2

Imagine we have a list of numbers and we want to create a dictionary where each number is a key, and its value is whether the number is even or odd.

Using a traditional for loop, it might look like this:

numbers = [1, 2, 3, 4, 5]
number_parity = {}
for number in numbers:
if number % 2 == 0:
number_parity[number] = "even"
else:
number_parity[number] = "odd"

print(number_parity) #Outputs: {1: 'odd', 2: 'even', 3: 'odd', 4: 'even', 5: 'odd'}

Now, let's do the same with a dictionary comprehension:

numbers = [1, 2, 3, 4, 5]
number_parity = {number: ("even" if number % 2 == 0 else "odd") for number in numbers}
print(number_parity) #Outputs: {1: 'odd', 2: 'even', 3: 'odd', 4: 'even', 5: 'odd'}

Generator Comprehension

Generator comprehensions are defined within parentheses (), following this basic format:

(expression for item in iterable)

You can also add a conditional statement into a generator comprehension to filter items based on a condition:

(expression for item in iterable if condition)

Generator comprehensions are particularly useful for creating iterators with minimal memory usage, as they generate items one by one, only as needed. Let's explore some practical examples to see generator comprehensions in action.

Example 1

Suppose we want to create a generator that can create any number of squared value.

Here's how we could achieve this with a traditional generator function:

def generate_squares(n):
for number in range(n):
yield number ** 2

squared_numbers_generator = generate_squares(10)

Here's how it looks with a generator comprehension:

squared_numbers = (number ** 2 for number in range(10))

Like the other comprehensions, generator comprehensions are very useful with creating readable and concise code.

Example 2

Suppose we want to create a generator that can create any number of even numbers.

Here's how we could achieve this with a traditional generator function:

def generate_even_numbers(n):
for i in range(n):
if i % 2 == 0:
yield i

even_numbers_generator = generate_even_numbers(11)

Here's how it looks with a generator comprehension:

even_numbers = (i for i in range(11) if i % 2 == 0)

Conclusion

These examples demonstrate how comprehensions can simplify common programming tasks. They're a lot like lambda expressions- an alternative way to write code that is useful for quick expressions and calculations.

That means they also suffer the same drawback- comprehensions aren't for complex expressions and calculations,and overusing them can lead to code that is difficult to read and understand, especially for those not deeply familiar with Python's syntax. Comprehensions are most effective when used to replace simple loops and conditional statements with a clear and concise alternative. When the logic becomes too intricate, breaking down the comprehension into a traditional loop or using functions may be more appropriate to maintain readability and ease of debugging.