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, written with curly braces and key: value pairs. Lists, tuples, and strings are indexed by integer position; dictionaries are looked up by key. There is no dict[0] meaning "first item" unless 0 happens to be a 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 never keeps two values for the same key.
scores = {'Ana': 91, 'Ana': 95}
print(scores) # {'Ana': 95}
The same overwrite behavior appears when assigning after creation, and you can add brand-new keys the same way:
scores['Ana'] = 100 # overwrite existing key
scores['Cy'] = 77 # add a new key
Since Python 3.7, dictionaries preserve insertion order, so iteration follows the order keys were first added.
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.
| Candidate key | Valid key? | Reason |
|---|---|---|
'id' | Yes | Strings are hashable |
42 | Yes | Integers are hashable |
3.14 | Yes | Floats are hashable |
True | Yes | Booleans are hashable |
(2026, 'PCEP') | Yes | Tuple of 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, the tuple is unhashable because one element is unhashable.
valid = {(1, 2): 'point'}
invalid = {([1, 2], 3): 'bad'} # TypeError: unhashable type: 'list'
One subtle trap: because True == 1 and False == 0, using True and 1 as separate keys collides. {1: 'a', True: 'b'} produces {1: 'b'} because they hash equal.
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 you supply, and never raises KeyError.
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 is heavily 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 view methods when you need a specific perspective:
| Method | Provides |
|---|---|
keys() | The keys |
values() | The values |
items() | Key-value pairs as tuples |
for key, value in profile.items():
print(key, value)
Mutating methods
Dictionaries are mutable: you can add, replace, and delete entries. Common methods include update(other) (merge in pairs), pop(key) (remove and return the value, KeyError if absent unless a default is given), popitem() (remove and return the last inserted pair), setdefault(key, default), and clear(). As with lists, update() and clear() 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, and notice when a method changes the dictionary before a later line reads it; the final answer often depends on the latest value bound to a key.
Building dictionaries and counting patterns
The PCEP exam tests several ways a dictionary is created and updated, and the most common scenario is accumulating counts or grouping data. A literal {'a': 1, 'b': 2} is the simplest form, but the dict() constructor also builds one from keyword arguments (dict(a=1, b=2)) or from an iterable of pairs (dict([('a', 1), ('b', 2)])). The setdefault(key, default) method is a frequent exam target: it returns the existing value if the key is present, otherwise it inserts the key with the default and returns that default, all in one step. This makes it ideal for counting without a KeyError.
For example, looping over 'banana' and writing counts[ch] = counts.get(ch, 0) + 1 builds {'b': 1, 'a': 3, 'n': 2}, where get supplies a starting value of 0 the first time each character is seen. The update() method merges another dictionary or iterable of pairs into the current one, overwriting any shared keys with the incoming values, and returns None. When tracing such code, track each key's current value line by line and remember that re-assigning an existing key never grows the dictionary; it only replaces the stored value.
Also recall that len(d) counts keys, not values, and that iterating with for k in d walks the keys in insertion order, which lets you predict printed output exactly when the snippet builds the dictionary step by step before looping over it.
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?