Dictionaries, Keys, Membership, and Iteration
Key Takeaways
- Dictionaries map unique keys to values, so each key can appear only once in a dictionary object.
- Dictionary keys must be hashable, which allows strings, numbers, booleans, and tuples of hashable values but rejects lists and sets.
- If a dictionary literal repeats a key, the later value overwrites the earlier value for that same key.
- The in operator checks dictionary keys, not values, unless you explicitly test values().
- Iterating directly over a dictionary produces keys; use items() when both keys and values are needed.
Mapping instead of position
A dictionary stores associations between keys and values. Lists, tuples, and strings are indexed by integer position. Dictionaries are looked up by key.
scores = {'Ana': 91, 'Bo': 84}
print(scores['Ana']) # 91
Keys are unique inside one dictionary. If code assigns a value to an existing key, the old value is replaced. The dictionary does not keep both values for that key.
scores = {'Ana': 91, 'Ana': 95}
print(scores) # {'Ana': 95}
That same overwrite behavior appears when assigning after creation:
scores['Ana'] = 100
Hashable keys
Dictionary lookup depends on hashing, so keys must be hashable. Immutable built-in values such as strings, integers, floats, booleans, and tuples of hashable elements can be keys. Mutable collections such as lists, dictionaries, and sets cannot be keys.
| Candidate key | Valid key? | Reason |
|---|---|---|
'id' | Yes | Strings are hashable |
42 | Yes | Integers are hashable |
(2026, 'PCEP') | Yes | Tuple contains hashable elements |
[2026, 'PCEP'] | No | Lists are mutable and unhashable |
{'x', 'y'} | No | Sets are mutable and unhashable |
A tuple is not automatically safe. If it contains a list, it is not hashable because one element is unhashable.
valid = {(1, 2): 'point'}
invalid = {([1, 2], 3): 'bad'} # TypeError
Lookup, get, and membership
Using square brackets with a missing key raises KeyError.
profile = {'name': 'Mina'}
print(profile['age']) # KeyError
get() is safer when a missing key is acceptable. It returns None by default or a fallback value that you provide.
print(profile.get('age')) # None
print(profile.get('age', 0)) # 0
Membership with in checks keys, not values.
profile = {'name': 'Mina', 'role': 'student'}
print('name' in profile) # True
print('Mina' in profile) # False
print('Mina' in profile.values()) # True
This rule is often tested because beginners read a dictionary as a bag of all contained data. Python treats direct membership as key membership.
Iteration views
Iterating over a dictionary directly yields keys.
for key in profile:
print(key)
Use dictionary view methods when you need a specific perspective.
| Method | Provides |
|---|---|
keys() | Keys |
values() | Values |
items() | Key-value pairs as tuples |
for key, value in profile.items():
print(key, value)
Dictionary views reflect the dictionary contents rather than making an ordinary independent list. For entry-level tracing, the practical point is simpler: choose the view that matches the question. If a question asks whether a value is present, direct dictionary membership is the wrong test. If it asks what a loop prints, decide whether the loop receives keys, values, or item tuples before reading the body.
Dictionaries are mutable. You can add, replace, and delete entries. Common methods include update(), pop(), and clear(). As with lists, many mutating methods return None, while pop(key) returns the removed value. For PCEP, always trace whether the code is reading a value, mutating the mapping, or testing only keys. Also notice when a method changes the dictionary before a later line reads it; the final answer often depends on the latest value associated with a key.
What is printed by this code? d = {'x': 1, 'x': 2}; print(d['x'])
For d = {'a': 10, 'b': 20}, what does 'a' in d test?
Which attempted dictionary key raises TypeError?