Creating Dictionaries
Dictionaries store key-value pairs. Keys must be unique and hashable. Values can be any type. As of Python 3.7, dicts maintain insertion order.
# Literal syntax
person = {
"name": "Alice",
"age": 30,
"city": "London",
}
# dict() constructor with keyword arguments
config = dict(host="localhost", port=5432, debug=False)
# dict() from list of key-value pairs
pairs = [("a", 1), ("b", 2), ("c", 3)]
d = dict(pairs) # {'a': 1, 'b': 2, 'c': 3}
# Empty dict
empty = {}
empty2 = dict()
# Keys can be any hashable type
mixed_keys = {
"string": 1,
42: "number key",
(1, 2): "tuple key",
True: "bool key", # True == 1, so this replaces key 1 (!)
}
# Nested dicts
user = {
"name": "Bob",
"address": {
"street": "123 Main St",
"city": "New York",
},
"scores": [95, 87, 92],
}
print(user["address"]["city"]) # New York
print(user["scores"][0]) # 95
# Check size and membership (checks keys by default)
print(len(person)) # 3
print("name" in person) # True - key exists
print("Alice" in person) # False - checks keys, not values
print("age" not in person) # False
Accessing and Modifying
person = {"name": "Alice", "age": 30}
# Access by key - raises KeyError if not found
print(person["name"]) # Alice
# print(person["xyz"]) # KeyError: 'xyz'
# get() - safe access with optional default
print(person.get("name")) # Alice
print(person.get("email")) # None (no KeyError)
print(person.get("email", "N/A")) # N/A (custom default)
# Add or update
person["city"] = "London" # add new key
person["age"] = 31 # update existing key
# setdefault() - set only if key does not exist
person.setdefault("country", "UK") # adds "country": "UK"
person.setdefault("age", 99) # age stays 31, not changed
print(person["country"]) # UK
print(person["age"]) # 31
# Delete
del person["city"] # raises KeyError if not found
value = person.pop("country") # remove and return; KeyError if missing
value2 = person.pop("xyz", None) # safe pop with default
print(person) # {'name': 'Alice', 'age': 31}
Dictionary Methods
d = {"a": 1, "b": 2, "c": 3}
# Views - dynamic (reflect changes)
print(d.keys()) # dict_keys(['a', 'b', 'c'])
print(d.values()) # dict_values([1, 2, 3])
print(d.items()) # dict_items([('a', 1), ('b', 2), ('c', 3)])
# Convert to lists if needed
keys_list = list(d.keys())
# update() - merge another dict into this one (in place)
d.update({"c": 30, "d": 4}) # c overwritten, d added
print(d) # {'a': 1, 'b': 2, 'c': 30, 'd': 4}
d.update(e=5, f=6) # keyword argument form
print(d) # {'a': 1, 'b': 2, 'c': 30, 'd': 4, 'e': 5, 'f': 6}
# pop() - remove and return
val = d.pop("f")
val2 = d.pop("xyz", "missing") # safe with default
print(val, val2) # 6 missing
# popitem() - remove and return an arbitrary (last in 3.7+) item
key, value = d.popitem()
print(key, value) # f 6 (last inserted)
# copy() - shallow copy
original = {"x": [1, 2, 3], "y": 10}
shallow = original.copy()
shallow["y"] = 99 # does not affect original
shallow["x"].append(4) # DOES affect original (shared list)
print(original) # {'x': [1, 2, 3, 4], 'y': 10}
# clear()
temp = {"k": "v"}
temp.clear()
print(temp) # {}
| Method | Description | Returns |
|---|---|---|
get(key, default) | Safe key access | value or default |
setdefault(key, val) | Set if key absent | existing or new value |
update(other) | Merge in place | None |
pop(key, default) | Remove and return | value |
popitem() | Remove last item | (key, value) |
keys() | Key view | dict_keys |
values() | Value view | dict_values |
items() | Key-value pair view | dict_items |
copy() | Shallow copy | dict |
clear() | Remove all items | None |
Iterating Dictionaries
scores = {"Alice": 95, "Bob": 87, "Charlie": 92}
# Iterate over keys (default)
for name in scores:
print(name) # Alice, Bob, Charlie
# Iterate over values
for score in scores.values():
print(score) # 95, 87, 92
# Iterate over key-value pairs (most common)
for name, score in scores.items():
print(f"{name}: {score}")
# Alice: 95
# Bob: 87
# Charlie: 92
# Sorted iteration
for name in sorted(scores):
print(f"{name}: {scores[name]}")
# Sort by value (highest first)
for name, score in sorted(scores.items(), key=lambda x: x[1], reverse=True):
print(f"{name}: {score}")
# Alice: 95
# Charlie: 92
# Bob: 87
# Filter while iterating - use comprehension to avoid RuntimeError
# Never modify a dict while iterating over it directly
active_users = {"Alice": True, "Bob": False, "Charlie": True}
active = {k: v for k, v in active_users.items() if v}
print(active) # {'Alice': True, 'Charlie': True}
Dict Comprehensions
# Basic: {key_expr: val_expr for item in iterable}
squares = {x: x**2 for x in range(1, 6)}
print(squares) # {1: 1, 2: 4, 3: 9, 4: 16, 5: 25}
# With condition
even_squares = {x: x**2 for x in range(1, 11) if x % 2 == 0}
print(even_squares) # {2: 4, 4: 16, 6: 36, 8: 64, 10: 100}
# Transform an existing dict
prices = {"apple": 0.5, "banana": 0.3, "cherry": 1.2}
with_tax = {k: round(v * 1.1, 2) for k, v in prices.items()}
print(with_tax) # {'apple': 0.55, 'banana': 0.33, 'cherry': 1.32}
# Invert a dict (swap keys and values)
original = {"a": 1, "b": 2, "c": 3}
inverted = {v: k for k, v in original.items()}
print(inverted) # {1: 'a', 2: 'b', 3: 'c'}
# Build dict from two lists using zip
keys = ["name", "age", "city"]
values = ["Alice", 30, "London"]
person = {k: v for k, v in zip(keys, values)}
# Simpler: dict(zip(keys, values))
print(person) # {'name': 'Alice', 'age': 30, 'city': 'London'}
# Group items by a property
words = ["apple", "banana", "cherry", "avocado", "blueberry"]
by_first_letter = {}
for word in words:
by_first_letter.setdefault(word[0], []).append(word)
print(by_first_letter)
# {'a': ['apple', 'avocado'], 'b': ['banana', 'blueberry'], 'c': ['cherry']}
Merging Dictionaries
defaults = {"color": "blue", "size": "medium", "font": "Arial"}
overrides = {"color": "red", "weight": "bold"}
# Python 3.9+: | operator (creates new dict)
merged = defaults | overrides
print(merged)
# {'color': 'red', 'size': 'medium', 'font': 'Arial', 'weight': 'bold'}
# overrides win on conflict (color: 'red', not 'blue')
# Python 3.9+: |= operator (updates in place)
config = {"debug": False, "port": 8080}
config |= {"port": 9000, "host": "localhost"}
print(config)
# {'debug': False, 'port': 9000, 'host': 'localhost'}
# Python 3.5+: ** unpacking (works in all Python 3 versions)
merged2 = {**defaults, **overrides} # same result as |
print(merged2)
# Multiple dicts
base = {"a": 1}
layer1 = {"b": 2}
layer2 = {"c": 3, "a": 99}
combined = {**base, **layer1, **layer2}
print(combined) # {'a': 99, 'b': 2, 'c': 3} - last wins
# update() - modifies in place
target = {"a": 1, "b": 2}
source = {"b": 20, "c": 30}
target.update(source)
print(target) # {'a': 1, 'b': 20, 'c': 30}
defaultdict and Counter
The collections module provides specialized dict subclasses for common patterns.
from collections import defaultdict, Counter, OrderedDict
# --- defaultdict ---
# Never raises KeyError - creates default value for missing keys
# Group words by first letter
words = ["apple", "banana", "cherry", "avocado", "blueberry"]
grouped = defaultdict(list) # default factory = list()
for word in words:
grouped[word[0]].append(word) # no KeyError, creates [] automatically
print(dict(grouped))
# {'a': ['apple', 'avocado'], 'b': ['banana', 'blueberry'], 'c': ['cherry']}
# Count occurrences (manual)
counter_dict = defaultdict(int) # default = 0
for word in words:
counter_dict[word[0]] += 1
print(dict(counter_dict)) # {'a': 2, 'b': 2, 'c': 1}
# Nested defaultdict
nested = defaultdict(lambda: defaultdict(int))
nested["fruits"]["apple"] += 1
nested["fruits"]["banana"] += 3
print(nested["fruits"]["apple"]) # 1
# --- Counter ---
# Specialized dict for counting hashable objects
text = "the quick brown fox jumps over the lazy dog"
word_count = Counter(text.split())
print(word_count)
# Counter({'the': 2, 'quick': 1, 'brown': 1, ...})
# Most common items
print(word_count.most_common(3))
# [('the', 2), ('quick', 1), ('brown', 1)]
# Count characters
char_count = Counter("banana")
print(char_count) # Counter({'a': 3, 'n': 2, 'b': 1})
# Counter arithmetic
c1 = Counter({"a": 3, "b": 1})
c2 = Counter({"a": 1, "b": 2, "c": 5})
print(c1 + c2) # Counter({'c': 5, 'a': 4, 'b': 3})
print(c1 - c2) # Counter({'a': 2}) (negatives dropped)
print(c1 & c2) # Counter({'a': 1, 'b': 1}) (min of each)
# --- OrderedDict ---
# Remembers insertion order (dicts do this too in 3.7+)
# Still useful for move_to_end() and order-sensitive equality
od = OrderedDict([("a", 1), ("b", 2), ("c", 3)])
od.move_to_end("a") # move to end
od.move_to_end("c", last=False) # move to front
print(list(od.keys())) # ['c', 'b', 'a']