Python - Dictionary Tutorial (Complete Guide)
Dictionaries can be created using curly braces, the `dict()` constructor, or dictionary comprehensions. Each method serves different use cases.
Key Insights
- Python dictionaries are mutable, unordered collections that store key-value pairs with O(1) average lookup time, making them ideal for fast data retrieval and mapping relationships
- Dictionary comprehensions, the
get()method with defaults, andsetdefault()provide cleaner, more Pythonic alternatives to traditional iteration and conditional logic - Modern Python (3.7+) preserves insertion order in dictionaries, enabling predictable iteration while maintaining hash table performance characteristics
Creating and Initializing Dictionaries
Dictionaries can be created using curly braces, the dict() constructor, or dictionary comprehensions. Each method serves different use cases.
# Literal syntax
user = {'name': 'Alice', 'age': 30, 'role': 'admin'}
# dict() constructor
config = dict(host='localhost', port=5432, timeout=30)
# From list of tuples
pairs = [('a', 1), ('b', 2), ('c', 3)]
mapping = dict(pairs)
# Dictionary comprehension
squares = {x: x**2 for x in range(1, 6)}
# {1: 1, 2: 4, 3: 9, 4: 16, 5: 25}
# Empty dictionary
cache = {}
# or
cache = dict()
The fromkeys() method creates dictionaries with default values for multiple keys:
# Initialize with None
keys = ['cpu', 'memory', 'disk']
metrics = dict.fromkeys(keys)
# {'cpu': None, 'memory': None, 'disk': None}
# Initialize with specific value
counters = dict.fromkeys(['success', 'error', 'pending'], 0)
# {'success': 0, 'error': 0, 'pending': 0}
Accessing and Modifying Values
Direct key access raises KeyError if the key doesn’t exist. The get() method provides safer access with optional defaults.
user = {'name': 'Bob', 'age': 25}
# Direct access
print(user['name']) # 'Bob'
# print(user['email']) # KeyError
# Safe access with get()
email = user.get('email') # None
email = user.get('email', 'no-email@example.com') # default value
# Modify existing key
user['age'] = 26
# Add new key
user['email'] = 'bob@example.com'
# Update multiple values
user.update({'role': 'developer', 'active': True})
The setdefault() method returns a value if the key exists, otherwise inserts the key with a default value:
word_count = {}
words = ['apple', 'banana', 'apple', 'cherry', 'banana', 'apple']
for word in words:
word_count.setdefault(word, 0)
word_count[word] += 1
# {'apple': 3, 'banana': 2, 'cherry': 1}
Dictionary Methods for Data Manipulation
The pop() and popitem() methods remove and return values, while clear() empties the dictionary.
settings = {'theme': 'dark', 'font_size': 14, 'auto_save': True}
# Remove specific key
theme = settings.pop('theme') # 'dark'
# settings is now {'font_size': 14, 'auto_save': True}
# Pop with default (no KeyError)
language = settings.pop('language', 'en') # 'en'
# Remove and return arbitrary key-value pair (LIFO in Python 3.7+)
item = settings.popitem() # ('auto_save', True)
# Clear all items
settings.clear() # {}
Iterating Over Dictionaries
Dictionaries provide three views: keys, values, and items. These views are dynamic and reflect changes to the dictionary.
inventory = {'apples': 50, 'bananas': 30, 'oranges': 45}
# Iterate over keys (default)
for item in inventory:
print(item)
# Explicit keys iteration
for item in inventory.keys():
print(f"{item}: {inventory[item]}")
# Iterate over values
for quantity in inventory.values():
print(quantity)
# Iterate over key-value pairs
for item, quantity in inventory.items():
print(f"{item}: {quantity}")
# Filter during iteration
low_stock = {k: v for k, v in inventory.items() if v < 40}
# {'bananas': 30}
Nested Dictionaries and Complex Structures
Dictionaries can contain other dictionaries, enabling hierarchical data structures.
users = {
'user_001': {
'name': 'Alice',
'permissions': ['read', 'write'],
'metadata': {'last_login': '2024-01-15', 'login_count': 42}
},
'user_002': {
'name': 'Bob',
'permissions': ['read'],
'metadata': {'last_login': '2024-01-14', 'login_count': 18}
}
}
# Access nested values
alice_permissions = users['user_001']['permissions']
bob_last_login = users['user_002']['metadata']['last_login']
# Safe nested access
def get_nested(d, *keys, default=None):
for key in keys:
if isinstance(d, dict):
d = d.get(key, default)
else:
return default
return d
login_count = get_nested(users, 'user_001', 'metadata', 'login_count') # 42
invalid = get_nested(users, 'user_999', 'name', default='Unknown') # 'Unknown'
Dictionary Comprehensions for Transformation
Dictionary comprehensions provide concise syntax for creating dictionaries from iterables with transformations and filters.
# Transform values
prices = {'apple': 0.5, 'banana': 0.3, 'orange': 0.6}
prices_cents = {item: price * 100 for item, price in prices.items()}
# {'apple': 50.0, 'banana': 30.0, 'orange': 60.0}
# Filter and transform
numbers = {'a': 1, 'b': 2, 'c': 3, 'd': 4, 'e': 5}
even_squares = {k: v**2 for k, v in numbers.items() if v % 2 == 0}
# {'b': 4, 'd': 16}
# Swap keys and values
original = {'a': 1, 'b': 2, 'c': 3}
swapped = {v: k for k, v in original.items()}
# {1: 'a', 2: 'b', 3: 'c'}
# Conditional values
scores = {'Alice': 85, 'Bob': 92, 'Charlie': 78}
grades = {name: 'Pass' if score >= 80 else 'Fail'
for name, score in scores.items()}
# {'Alice': 'Pass', 'Bob': 'Pass', 'Charlie': 'Fail'}
Merging and Combining Dictionaries
Python 3.9+ introduces the merge operator | and update operator |= for combining dictionaries.
defaults = {'timeout': 30, 'retries': 3, 'debug': False}
user_config = {'timeout': 60, 'debug': True}
# Merge with | operator (Python 3.9+)
config = defaults | user_config
# {'timeout': 60, 'retries': 3, 'debug': True}
# Update in place with |=
defaults |= user_config
# Pre-3.9: unpacking
config = {**defaults, **user_config}
# Update method (modifies in place)
defaults.update(user_config)
# Merge multiple dictionaries
db_config = {'host': 'localhost', 'port': 5432}
auth_config = {'user': 'admin', 'password': 'secret'}
full_config = defaults | db_config | auth_config
defaultdict for Automatic Default Values
The collections.defaultdict eliminates the need for existence checks when building dictionaries.
from collections import defaultdict
# Group items by category
items = [
('fruit', 'apple'),
('vegetable', 'carrot'),
('fruit', 'banana'),
('vegetable', 'spinach'),
('fruit', 'orange')
]
# Traditional approach
groups = {}
for category, item in items:
if category not in groups:
groups[category] = []
groups[category].append(item)
# With defaultdict
groups = defaultdict(list)
for category, item in items:
groups[category].append(item)
# defaultdict(<class 'list'>, {'fruit': ['apple', 'banana', 'orange'],
# 'vegetable': ['carrot', 'spinach']})
# Count occurrences
text = "the quick brown fox jumps over the lazy dog"
word_count = defaultdict(int)
for word in text.split():
word_count[word] += 1
OrderedDict and Specialized Dictionary Types
While standard dictionaries maintain insertion order in Python 3.7+, OrderedDict provides additional methods and guarantees.
from collections import OrderedDict
# OrderedDict with move_to_end
cache = OrderedDict([('a', 1), ('b', 2), ('c', 3)])
cache.move_to_end('a') # Move to end
# OrderedDict([('b', 2), ('c', 3), ('a', 1)])
cache.move_to_end('b', last=False) # Move to beginning
# OrderedDict([('b', 2), ('c', 3), ('a', 1)])
# Counter for frequency counting
from collections import Counter
words = ['apple', 'banana', 'apple', 'cherry', 'banana', 'apple']
counter = Counter(words)
# Counter({'apple': 3, 'banana': 2, 'cherry': 1})
print(counter.most_common(2)) # [('apple', 3), ('banana', 2)]
counter.update(['apple', 'date']) # Add more counts
Performance Considerations and Best Practices
Dictionary operations have specific performance characteristics that affect design decisions.
import sys
# Memory efficiency: dict vs list of tuples
data_dict = {i: i**2 for i in range(1000)}
data_list = [(i, i**2) for i in range(1000)]
print(sys.getsizeof(data_dict)) # ~36968 bytes
print(sys.getsizeof(data_list)) # ~9016 bytes (but O(n) lookup)
# Use membership testing efficiently
cache = {'key1': 'value1', 'key2': 'value2'}
# Fast: O(1)
if 'key1' in cache:
print(cache['key1'])
# Slow: O(n)
if 'value1' in cache.values():
print('found')
# Pre-compute reverse lookup if needed
reverse_cache = {v: k for k, v in cache.items()}
Dictionaries are fundamental to Python programming, appearing in JSON parsing, configuration management, caching, and data transformation. Understanding their methods, performance characteristics, and specialized variants enables you to write efficient, idiomatic Python code.