Python Dictionary Comprehensions with Examples
Dictionary comprehensions are Python's elegant solution for creating dictionaries programmatically. They follow the same syntactic pattern as list comprehensions but produce key-value pairs instead...
Key Insights
- Dictionary comprehensions provide a concise, readable syntax for creating dictionaries in a single line, often outperforming traditional for-loop approaches by 20-30%
- Conditional logic can be embedded directly into comprehensions using if statements for filtering and if-else expressions for conditional value assignment
- While powerful, dict comprehensions should prioritize readability over cleverness—complex transformations are better handled with explicit loops
Introduction to Dictionary Comprehensions
Dictionary comprehensions are Python’s elegant solution for creating dictionaries programmatically. They follow the same syntactic pattern as list comprehensions but produce key-value pairs instead of simple values. The basic syntax is {key_expression: value_expression for item in iterable}.
The primary advantage is conciseness without sacrificing readability. A dictionary comprehension replaces 3-4 lines of traditional loop code with a single, clear expression. Beyond aesthetics, comprehensions often execute faster than equivalent loops because they’re optimized at the bytecode level.
Here’s a direct comparison:
# Traditional approach
squares = {}
for x in range(5):
squares[x] = x ** 2
# Dictionary comprehension
squares = {x: x ** 2 for x in range(5)}
# Result: {0: 0, 1: 1, 2: 4, 3: 9, 4: 16}
Both produce identical results, but the comprehension is more compact and expresses intent more clearly: “create a dictionary mapping numbers to their squares.”
Basic Dictionary Comprehension Patterns
The most common pattern is transforming a list into a dictionary. You’ll frequently need to create mappings from sequential data.
# List to dictionary with enumeration
fruits = ['apple', 'banana', 'cherry']
fruit_index = {fruit: idx for idx, fruit in enumerate(fruits)}
# Result: {'apple': 0, 'banana': 1, 'cherry': 2}
# String list to length mapping
words = ['cat', 'elephant', 'dog']
word_lengths = {word: len(word) for word in words}
# Result: {'cat': 3, 'elephant': 8, 'dog': 3}
Transforming existing dictionaries is equally straightforward. You can modify keys, values, or both:
prices = {'apple': 0.50, 'banana': 0.30, 'cherry': 0.75}
# Convert prices to cents
prices_cents = {fruit: int(price * 100) for fruit, price in prices.items()}
# Result: {'apple': 50, 'banana': 30, 'cherry': 75}
# Uppercase all keys
upper_prices = {fruit.upper(): price for fruit, price in prices.items()}
# Result: {'APPLE': 0.5, 'BANANA': 0.3, 'CHERRY': 0.75}
Inverting a dictionary (swapping keys and values) is a classic use case:
original = {'a': 1, 'b': 2, 'c': 3}
inverted = {value: key for key, value in original.items()}
# Result: {1: 'a', 2: 'b', 3: 'c'}
Warning: This only works reliably when values are unique and hashable. Duplicate values will result in lost data since dictionary keys must be unique.
Conditional Dictionary Comprehensions
Adding conditional logic dramatically increases the power of dictionary comprehensions. Use if clauses to filter which items get included:
numbers = {'a': 1, 'b': 2, 'c': 3, 'd': 4, 'e': 5}
# Filter even values only
even_only = {k: v for k, v in numbers.items() if v % 2 == 0}
# Result: {'b': 2, 'd': 4}
# Filter keys that start with specific letter
a_words = {k: v for k, v in word_lengths.items() if k.startswith('c')}
For conditional value assignment (not filtering), use the ternary operator:
scores = {'Alice': 85, 'Bob': 72, 'Charlie': 90, 'Diana': 68}
# Assign pass/fail based on threshold
results = {name: 'Pass' if score >= 70 else 'Fail'
for name, score in scores.items()}
# Result: {'Alice': 'Pass', 'Bob': 'Pass', 'Charlie': 'Pass', 'Diana': 'Fail'}
# Apply discount to high-value items
discounted = {item: price * 0.9 if price > 0.50 else price
for item, price in prices.items()}
You can combine multiple conditions using and and or:
# Only include passing scores above 80
high_performers = {name: score for name, score in scores.items()
if score >= 70 and score > 80}
# Result: {'Alice': 85, 'Charlie': 90}
Advanced Techniques
Nested dictionary comprehensions create multi-level dictionaries, useful for representing hierarchical data:
# Create a multiplication table
multiplication_table = {
i: {j: i * j for j in range(1, 4)}
for i in range(1, 4)
}
# Result: {1: {1: 1, 2: 2, 3: 3}, 2: {1: 2, 2: 4, 3: 6}, 3: {1: 3, 2: 6, 3: 9}}
The zip() function elegantly combines multiple iterables:
keys = ['name', 'age', 'city']
values = ['Alice', 30, 'NYC']
person = {k: v for k, v in zip(keys, values)}
# Result: {'name': 'Alice', 'age': 30, 'city': 'NYC'}
# Merge two lists with transformation
items = ['apple', 'banana', 'cherry']
quantities = [5, 3, 8]
inventory = {item.upper(): qty * 10 for item, qty in zip(items, quantities)}
# Result: {'APPLE': 50, 'BANANA': 30, 'CHERRY': 80}
Combining with enumerate() when you need both index and value:
tasks = ['Write code', 'Review PR', 'Deploy']
task_dict = {f"task_{idx}": task for idx, task in enumerate(tasks, start=1)}
# Result: {'task_1': 'Write code', 'task_2': 'Review PR', 'task_3': 'Deploy'}
Common Use Cases and Real-World Examples
Dictionary comprehensions excel at data transformation tasks. Consider parsing configuration data:
# Convert config list to lookup dictionary
config_pairs = [
"DB_HOST=localhost",
"DB_PORT=5432",
"DB_NAME=myapp"
]
config = {item.split('=')[0]: item.split('=')[1]
for item in config_pairs}
# Result: {'DB_HOST': 'localhost', 'DB_PORT': '5432', 'DB_NAME': 'myapp'}
Grouping items by category is a frequent requirement:
transactions = [
{'item': 'apple', 'category': 'fruit', 'price': 1.20},
{'item': 'carrot', 'category': 'vegetable', 'price': 0.80},
{'item': 'banana', 'category': 'fruit', 'price': 0.90},
]
# Group by category (simplified - only keeps last item per category)
by_category = {t['category']: t['item'] for t in transactions}
# Better approach for actual grouping uses defaultdict or setdefault
# But for unique category lookups, comprehensions work well
category_totals = {}
for t in transactions:
category = t['category']
category_totals[category] = category_totals.get(category, 0) + t['price']
# Create lookup table from ID-based data
users = [
{'id': 101, 'name': 'Alice'},
{'id': 102, 'name': 'Bob'},
]
user_lookup = {u['id']: u['name'] for u in users}
# Result: {101: 'Alice', 102: 'Bob'}
Performance Considerations and Best Practices
Dictionary comprehensions generally outperform equivalent for-loops. Here’s a performance comparison:
import timeit
# Setup code
setup = "data = range(10000)"
# Traditional loop
loop_code = """
result = {}
for i in data:
result[i] = i ** 2
"""
# Comprehension
comp_code = "result = {i: i ** 2 for i in data}"
loop_time = timeit.timeit(loop_code, setup=setup, number=1000)
comp_time = timeit.timeit(comp_code, setup=setup, number=1000)
print(f"Loop: {loop_time:.4f}s")
print(f"Comprehension: {comp_time:.4f}s")
# Comprehension is typically 20-30% faster
However, readability trumps performance for most applications. If a comprehension becomes difficult to understand, break it into explicit steps:
# Too complex - hard to understand at a glance
result = {k: v * 2 if v > 10 else v / 2 for k, v in data.items()
if k.startswith('x') and len(k) > 3}
# Better - explicit and clear
filtered_items = ((k, v) for k, v in data.items()
if k.startswith('x') and len(k) > 3)
result = {k: v * 2 if v > 10 else v / 2 for k, v in filtered_items}
For very large datasets, consider memory implications. Dictionary comprehensions create the entire dictionary in memory at once. If processing millions of records, generator expressions or iterative processing may be more appropriate.
Common Pitfalls and How to Avoid Them
Duplicate keys silently overwrite earlier values:
# Problem: Last value wins
items = [('a', 1), ('b', 2), ('a', 3)]
bad_dict = {k: v for k, v in items}
# Result: {'a': 3, 'b': 2} - first 'a' value is lost!
# Solution: Handle duplicates explicitly
from collections import defaultdict
good_dict = defaultdict(list)
for k, v in items:
good_dict[k].append(v)
# Result: {'a': [1, 3], 'b': [2]}
Over-nesting creates unreadable code:
# Anti-pattern: Too nested
bad = {x: {y: {z: x*y*z for z in range(3)} for y in range(3)} for x in range(3)}
# Better: Use helper functions or explicit loops
def create_layer(x, y):
return {z: x*y*z for z in range(3)}
good = {x: {y: create_layer(x, y) for y in range(3)} for x in range(3)}
Forgetting that keys must be hashable:
# This fails - lists aren't hashable
items = [[1, 2], [3, 4]]
# bad = {item: len(item) for item in items} # TypeError!
# Solution: Convert to tuples
good = {tuple(item): len(item) for item in items}
Dictionary comprehensions are a powerful tool in Python’s arsenal. Master the basics, use conditionals judiciously, and always prioritize code clarity. When in doubt, write it out explicitly first, then refactor to a comprehension only if it improves readability.