Python Dictionaries: Keys, Values and Lookup
A dictionary maps keys to values and finds any value in roughly constant time. It is the most used container in Python and the structure most real data arrives in.
- Basics
- Data Types
- Operators
- Strings
- Control Flow
- Lists
- Tuples
- Sets
- Dictionaries
- Comprehensions
- Functions
- Advanced Functions
- Recursion
- Exception Handling
- File Handling
- Modules
- Standard Library
- OOP
- Advanced OOP
- Iterators and Generators
- Decorators
- Context Managers
- Descriptors and Dataclasses
- Python Internals
- Concurrency
- Regular Expressions
- Serialization
- Command Line Python
- Testing and Debugging
- Type Hints
- Performance
- Python Security
- DSA with Python
Creating a dictionary
empty = {}
empty_too = dict()
person = {"name": "Meera", "age": 27, "city": "Pune"}
from_pairs = dict([("a", 1), ("b", 2)])
from_kwargs = dict(name="Meera", age=27)
from_zip = dict(zip(["a", "b"], [1, 2]))
from_keys = dict.fromkeys(["a", "b"], 0) # {'a': 0, 'b': 0}
print(person)dict.fromkeys(keys, []) gives every key the same list object. Appending through one key changes them all. Use a comprehension instead when the default is mutable.Keys must be hashable
valid = {
"text": 1,
42: "a number key",
(0, 0): "a tuple key",
True: "a boolean key",
None: "a None key",
}
# invalid = {[1, 2]: "x"} # TypeError: unhashable type: 'list'
# invalid = {{"a": 1}: "x"} # TypeError: unhashable type: 'dict'Values may be anything at all, including lists, dictionaries and functions. Only keys are restricted.
Reading a value
person = {"name": "Meera", "age": 27}
print(person["name"]) # Meera
# print(person["email"]) # KeyError: 'email'
print(person.get("email")) # None - no error
print(person.get("email", "unknown")) # unknown - your own default| Key present | Key missing | Use when | |
|---|---|---|---|
d[key] | the value | KeyError | The key must exist; a missing one is a bug |
d.get(key) | the value | None | Absence is normal |
d.get(key, x) | the value | x | You have a sensible fallback |
Do not reach for get automatically. If a missing key means your data is wrong, the KeyError is doing you a favour by stopping immediately.
Adding and updating
person = {"name": "Meera"}
person["age"] = 27 # add a new key
person["name"] = "Meera Nair" # overwrite an existing one
print(person)
person.update({"city": "Pune", "age": 28}) # add or overwrite several
person.update(role="engineer") # keyword form
print(person)
# Merge, Python 3.9 and later
defaults = {"theme": "light", "size": 12}
overrides = {"size": 14}
print(defaults | overrides) # {'theme': 'light', 'size': 14}
defaults |= overrides # in place merge
print(defaults)On a duplicate key, the value on the right wins. That is what makes this the standard way to apply overrides on top of defaults.
Removing
person = {"name": "Meera", "age": 27, "city": "Pune", "temp": 1}
age = person.pop("age") # removes and returns
print(age) # 27
missing = person.pop("email", None) # a default stops the KeyError
print(missing) # None
del person["temp"]
# del person["nope"] # KeyError
last = person.popitem() # removes and returns the LAST pair
print(last)
person.clear()
print(person) # {}Checking for a key
person = {"name": "Meera", "age": None}
print("name" in person) # True
print("email" not in person) # True
print(27 in person) # False - `in` checks KEYS, not values
print(27 in person.values()) # for values, say so
# A present key with a falsy value
print("age" in person) # True
print(person.get("age")) # None - present, but emptyOrder is guaranteed
d = {}
d["z"] = 1
d["a"] = 2
d["m"] = 3
print(list(d)) # ['z', 'a', 'm'] - insertion order, alwaysSince Python 3.7 dictionaries preserve insertion order as a language guarantee. Overwriting a value keeps the key in its original position; deleting and re-adding moves it to the end.
Iterating
ages = {"Meera": 27, "Arun": 31, "Sara": 24}
for key in ages: # keys by default
print(key)
for key in ages.keys(): # explicit, same thing
print(key)
for value in ages.values():
print(value)
for key, value in ages.items(): # the usual form
print(f"{key} is {value}")
for name, age in sorted(ages.items(), key=lambda pair: pair[1]):
print(name, age) # sorted by ageThe views are live
ages = {"Meera": 27}
keys = ages.keys()
ages["Arun"] = 31
print(list(keys)) # ['Meera', 'Arun'] - the view updated itself
print(ages.keys() & {"Meera", "Zara"}) # {'Meera'} - key views act like setsDo not resize while iterating
scores = {"a": 1, "b": 0, "c": 2}
# for key in scores:
# if scores[key] == 0:
# del scores[key] # RuntimeError: dictionary changed size
for key in list(scores): # iterate over a snapshot of the keys
if scores[key] == 0:
del scores[key]
print(scores) # {'a': 1, 'c': 2}
# Or build a new dictionary
scores = {k: v for k, v in scores.items() if v != 0}Nested dictionaries
users = {
"meera": {"age": 27, "roles": ["admin", "editor"]},
"arun": {"age": 31, "roles": ["viewer"]},
}
print(users["meera"]["age"]) # 27
print(users["meera"]["roles"][0]) # admin
print(users.get("zara", {}).get("age")) # None - safe at both levels
for name, details in users.items():
print(f"{name}: {details['age']}, {', '.join(details['roles'])}")Common mistakes
- Using
d[key]on data that may not contain the key. - Using
geteverywhere, hiding genuine bugs behind aNone. - Assuming
insearches values. - Deleting keys while iterating.
- Using
dict.fromkeys(keys, [])and sharing one list across every key. - Using a list as a key.
Best practices
- Use
d[key]when the key must exist,getwhen it may not. - Use
.items()whenever the loop needs both parts. - Iterate over
list(d)when the loop will delete keys. - Use
|to layer overrides on defaults. - Keep nesting shallow. Past two levels, a class or a dataclass usually reads better.
Practice
- Build a dictionary of five products and prices, then print them sorted by price.
- Explain the difference between
d["x"]andd.get("x")for a missing key, and when each is right. - Merge a defaults dictionary with a user settings dictionary so that user settings win.
- Remove every entry with a zero value, in two different ways.
- Explain why
dict.fromkeys(["a", "b"], [])is dangerous, and give a safe alternative.
Conclusion
A dictionary maps hashable keys to any values, keeps insertion order, and finds a value without scanning. Choose between [] and get deliberately, iterate with .items(), and never change the size of a dictionary you are looping over.