Intermediate Python Application and Design
Pythonic Data Structures
A More Pythonic Way
You already know how to build a list or a dictionary piece by piece using a for loop. It's reliable, clear, and gets the job done. But as you handle more complex data, these loops can start to feel clunky. There’s a more concise, elegant, and often faster way to create collections in Python: comprehensions.
Comprehensions allow you to build a new list, dictionary, or set from an existing iterable in a single, readable line of code.
Let’s look at a familiar task: creating a list of the first five square numbers. The standard loop approach looks something like this:
squares = []
for i in range(1, 6):
squares.append(i * i)
# squares is now [1, 4, 9, 16, 25]
It works perfectly well. But with a list comprehension, we can achieve the same result in one line:
squares = [i * i for i in range(1, 6)]
# squares is also [1, 4, 9, 16, 25]
This isn't just about saving space. It’s about writing code that more closely matches the thought process: “I want a new list where each item is i * i for every i in my range.” This declarative style is a core part of writing “Pythonic” code.
Filtering on the Fly
List comprehensions become even more powerful when you add conditions. You can filter the items from the original iterable as you build the new list. The structure is simple: just add an if statement at the end.
[expression for item in iterable if condition]
Imagine you have a list of sensor readings, and you only want to process the positive values. A comprehension makes this incredibly clean. Let's say we want to get the squares of only the odd numbers from 1 to 10.
odd_squares = [n**2 for n in range(1, 11) if n % 2 != 0]
# odd_squares will be [1, 9, 25, 49, 81]
This single line reads like a sentence:
You can even use a ternary operator if you need an if-else condition. This lets you change the expression itself based on a condition, rather than just filtering.
# Label numbers as 'even' or 'odd'
labels = [f"{i} is even" if i % 2 == 0 else f"{i} is odd" for i in range(5)]
# labels: ['0 is even', '1 is odd', '2 is even', '3 is odd', '4 is even']
Dictionaries and Sets Too
This concise syntax isn't limited to lists. You can apply the same logic to build dictionaries and sets, just by changing the brackets.
For a dictionary comprehension, you provide both a key and a value, separated by a colon, inside curly braces.
# Create a dictionary mapping numbers to their squares
num_to_square = {x: x**2 for x in range(1, 6)}
# Result: {1: 1, 2: 4, 3: 9, 4: 16, 5: 25}
For a set comprehension, you provide a single expression, also inside curly braces. Since sets only store unique elements, this is a fantastic way to find unique values in a collection.
words = ['apple', 'banana', 'apricot', 'blueberry', 'cherry']
# Find the unique first letters of the words
unique_starts = {word[0] for word in words}
# Result: {'a', 'b', 'c'}
Smarter Dictionary Handling
Beyond creating dictionaries, Python offers robust tools for working with them. When dealing with real-world data, like API responses, you can't always guarantee a key will be present. Trying to access a missing key with square brackets (my_dict['missing_key']) raises a KeyError.
A safer way is the .get() method. It retrieves a value just like bracket notation, but if the key is missing, it returns None by default instead of crashing your programme. You can also provide a second argument to specify a different default value.
user_prefs = {'theme': 'dark', 'font_size': 14}
# Safely get values
theme = user_prefs.get('theme') # 'dark'
language = user_prefs.get('language', 'en') # 'en', as 'language' is missing
What if you want to set a default value in the dictionary only if the key is missing? That's what setdefault() is for. It's like .get(), but it also inserts the key with the default value if it wasn't there.
config = {'retries': 3}
# 'timeout' is missing, so it's set to 60 and 60 is returned
timeout = config.setdefault('timeout', 60)
print(config) # {'retries': 3, 'timeout': 60}
Finally, merging dictionaries has become much cleaner. Before Python 3.9, you might have used the .update() method. Now, you can use the pipe (|) operator to combine two dictionaries, creating a new one.
base_settings = {'user': 'admin', 'permissions': 'read'}
default_settings = {'permissions': 'none', 'theme': 'light'}
# The pipe operator merges them. Values from the right-hand dict win.
merged = default_settings | base_settings
# Result: {'permissions': 'read', 'theme': 'light', 'user': 'admin'}
These Pythonic techniques help you write code that is not only shorter but also more expressive and robust. By handling data structures elegantly, you can focus more on the logic of your application.
Which list comprehension is equivalent to the following code?
squares = []
for i in range(5):
squares.append(i * i)
What is the primary purpose of a set comprehension?