A ChainMap
is a data structure in the collections module that allows for multiple mappings( mostly dictionaries) to be linked together and treated like a single unit.
In a single ChainMap
multiple dictionaries are connected in a chain-like manner. This allows for efficient lookup of values, since each dictionary is searched in turn, going from the first to the last. When a key is found in a dictionary, the corresponding value is returned and the search is terminated. If the key is not found in any of the dictionaries, an exception is raised.
The ChainMap
keeps references to the original dictionaries, this means that if any of the underlying dictionaries are modified, those changes will be reflected in the ChainMap
.
Instantiating ChainMaps
To create a ChainMap
, we start by importing the class from the collections module then use the following syntax.
ChainMap(*maps)
Where the parameter maps represents the arbitrary dict objects(or other mappings).
Create a ChainMap
#import the ChainMap class
from collections import ChainMap
d1 = {'one': 1, 'two': 2, 'three': 3}
d2 = {'four': 4, 'five': 5, 'six': 6}
d3 = {'seven': 7, 'eight': 8, 'nine': 9}
chain = ChainMap(d1, d2, d3)
print(chain)
Any modifications done to any of the linked dictionaries will be reflected in the ChainMap
.
#import the ChainMap class
from collections import ChainMap
d1 = {'one': 1, 'two': 2, 'three': 3}
d2 = {'four': 4, 'five': 5, 'six': 6}
d3 = {'seven': 7, 'eight': 8, 'nine': 9}
chain = ChainMap(d1, d2, d3)
d1['zero'] = 0
print(chain)
Add a new dictionary to the Chainmap
The new_child()
method adds a new dictionary at the beginning of the chain and returns a new ChainMap
reflecting the change.
#import the ChainMap class
from collections import ChainMap
d1 = {'one': 1, 'two':2}
d2 = {'three': 3, 'four': 4}
chain = ChainMap(d1, d2)
newd = {'ten': 10, 'twenty': 20}
chain = chain.new_child(newd)
print(chain)
Accessing values by their keys
When we access an element from the ChainMap
the element is looked up in the first map in the chain, if it is not found, then the lookup is continued in the next map in the chain (if any). This process continues until the element is found in one of the maps. If the element is not found in any of the maps, then just like in dictionaries, a KeyError
is raised.
#import the ChainMap class
from collections import ChainMap
d1 = {'one': 1, 'two': 2, 'three': 3}
d2 = {'four': 4, 'five': 5, 'six': 6}
d3 = {'seven': 7, 'eight': 8, 'nine': 9}
chain = ChainMap(d1, d2, d3)
print(chain['three'])
print(chain['five'])
print(chain['eight'])
print(chain['nine'])
In the above example, we used the bracket notation to access some values. This approach will raise a KeyError
if an item with the given key does not exist in any of the dictionaries.
#import the ChainMap class
from collections import ChainMap
d1 = {'one': 1, 'two': 2, 'three': 3}
d2 = {'four': 4, 'five': 5, 'six': 6}
d3 = {'seven': 7, 'eight': 8, 'nine': 9}
chain = ChainMap(d1, d2, d3)
print(chain['ten']) #non-existent
In the above example, the key "ten"
does not exist in the ChainMap
leading to the exception being raised.
The get()
method is the functional equivalent to the brackets notation. It allows us to access a value whose key is given as argument. This approach has an advantage in that it allows us to specify a default value that will be returned in case the key does not exist in any of the linked dictionaries, this allows us to avoid the KeyError
exception without using extra exception handling.
#import the ChainMap class
from collections import ChainMap
d1 = {'one': 1, 'two': 2, 'three': 3}
d2 = {'four': 4, 'five': 5, 'six': 6}
d3 = {'seven': 7, 'eight': 8, 'nine': 9}
chain = ChainMap(d1, d2, d3)
print(chain.get('two'))
print(chain.get('six'))
print(chain.get('seven'))
print(chain.get('ten', 'not found'))
In the above example, we used the get()
method to access values by their keys. Note that in the last statement, the key 'ten'
does not exist, we provided a default value i.e 'not found'
which is returned instead of raising the KeyError
exception.
Inserting and removing and modifying items
Removal, insertion and modification operations only happens to the first dictionary in the ChainMap
. This is very important to note as it can have some some unexpected outcomes. Consider the following example
#import the ChainMap class
from collections import ChainMap
d1 = {'one': 1, 'two': 2}
d2 = {'four': 4, 'five': 5}
d3 = {'two': 7, 'eight': 8}
chain = ChainMap(d1, d2, d3)
chain['thirty'] = 30
chain['eight'] = 8000
print(chain)
print(d1)
Note that in the above example, chain['thirty'] =30
led the new item to be added to the first dictionary. But wait, what about chain['eight'] = 8000
? in normal situation we would have expected that this operation to just update the item with key 'eight'
in the third dictionary, however, the item was added as a new item to the first dictionary as well.
Trying to remove an item existing in other dictionaries(other than the first in the ChainMap
) will lead to a KeyError
. Consider the following example:
#import the ChainMap class
from collections import ChainMap
d1 = {'one': 1, 'two': 2, 'three': 3}
d2 = {'four': 4, 'five': 5, 'six': 6}
d3 = {'two': 7, 'eight': 8, 'nine': 9}
chain = ChainMap(d1, d2, d3)
del chain['two']
del chain['five']
print(chain)
As shown above, the key 'two'
was removed successfully but 'five'
lead to a KeyError
saying that the key does not exist.
Due to the inconveniences shown above, it is better to perform operations that modify the ChainMap
directly on the dictionaries themselves rather than through the ChainMap
, the change will be reflected in the ChainMap
anyway.
#import the ChainMap class
from collections import ChainMap
d1 = {'one': 1, 'two': 2, 'three': 3}
d2 = {'four': 4, 'five': 5, 'six': 6}
d3 = {'two': 7, 'eight': 8, 'nine': 9}
chain = ChainMap(d1, d2, d3)
del d1['two']
del d2['five']
del d3['nine']
print(chain)
ChainMap information
The ChainMap
class defines the items()
, keys()
, and values()
methods which returns the items, the keys and the values in the ChainMap
, respectively.
from collections import ChainMap
Europe = {'Germany': 'Berlin', 'France': 'Paris', 'Spain': 'Madrid'}
Africa = {'Nairobi': 'Kenya', 'Rwanda': 'Kigali', 'Nigeria': 'Abuja'}
Asia = {'Japan': 'Tokyo', 'China': 'Beijing', 'India': 'Delhi'}
chain = ChainMap(Europe, Africa, Asia)
print("items:")
print(chain.items(), '\n')
print("keys:")
print(chain.keys(), '\n')
print("values:")
print(chain.values(), '\n')