×

Search anything:

ChainMap in Python

Binary Tree book by OpenGenus

Open-Source Internship opportunity by OpenGenus for programmers. Apply now.

In this article, we will explore the ChainMap container which is available in the collections module in Python.

Table of Contents:

  1. What is a Chain Map
  2. Creating a Chain Map
  3. Operations on Chain Map
  4. Iterating over a Chain Map
  5. Chain Map Complexity Analysis
  6. Applications of Chain Map
  7. Practice Questions

What is a Chain Map

Python's ChainMap container provides a way to group multiple dictionaries into one so that they appear as a single dictionary. We can perform all the operations on a ChainMap that we normally perform on a dictionary.

Internally, ChainMap creates a list of references to the original dictionaries.

chainmap in Python

Creating a ChainMap

In order to start using chain map, we must first import it from the collections module.

from collections import ChainMap

Consider 2 dictionaries A and B. We can create a chain map as follows

A = { "pen" : 15, "eraser" : 22 }
B = { "marker" : 10 }

chain = ChainMap(A, B)

print(chain)

Output :

ChainMap({'pen': 15, 'eraser': 22}, {'marker': 10})

Operations on Chain Map

Accessing elements using keys

Once created you can access the elements of the ChainMap just like you would in a dictionary or using the .get() method of the ChainMap object.

print( chain["pen"] )
print( chain.get("marker") )

Output :

15
10

It is important to note that if the same key is present in multiple dictionaries that make up a ChainMap, you will only get the first occurrence of the key. This depends upon the order in which the dictionaries were passed during the creation of the ChainMap object.

A = { "pen" : 15, "eraser" : 22 }
B = { "marker" : 10, "eraser" : 40 }

chain = ChainMap(A, B)
print( chain["eraser"] )

Output :

22

Adding a new dictionary to Chain Map

Since ChainMap internally uses a list, you can use the .maps.append() method to add a dictionary to the end of the Chain.

A = { "pen" : 15, "eraser" : 22 }
B = { "marker" : 10, "eraser" : 40 }

chain = ChainMap(A, B)
print(chain)

C = { "chart" : 50 }

chain.maps.append(C)
print(chain)

Output

ChainMap({'pen': 15, 'eraser': 22}, {'marker': 10, 'eraser': 40})
ChainMap({'pen': 15, 'eraser': 22}, {'marker': 10, 'eraser': 40}, {'chart': 50})

To add a new dictionary to the beginning of the chain use the .new_child() method.

A = { "pen" : 15, "eraser" : 22 }
B = { "marker" : 10, "eraser" : 40 }

chain = ChainMap(A, B)
print(chain)

C = { "chart" : 50 }
chain = chain.new_child(C)
print(chain)

Output

ChainMap({'pen': 15, 'eraser': 22}, {'marker': 10, 'eraser': 40})
ChainMap({'chart': 50}, {'pen': 15, 'eraser': 22}, {'marker': 10, 'eraser': 40})

Mutation Operations

ChainMap also supports operations like adding, updating, deleting key-value pairs. However, these operations only affect the first dictionary in the ChainMap even if the key is present in multiple dictionaries.

Adding a new pair

# Adding a new pair
chain["crayon"] = 5 
print(chain)

Output :

ChainMap({'pen': 15, 'eraser': 22, 'crayon': 5}, {'marker': 10, 'eraser': 40})

Updating existing pair

#Updating a key-value pair 
chain["eraser"]=100
print( chain )

Output

ChainMap({'eraser': 22}, {'marker': 10, 'eraser': 40})

Deleting a key-value pair

#Deleting a key-value pair 
del chain["pen"]
print( chain )

Output

ChainMap({'eraser': 22}, {'marker': 10, 'eraser': 40})

IMPORTANT!
Modifying the ChainMap using mutation operation will also modify the original dictionary and vice versa. This behavior is because ChainMap stores just a reference to the original dictionary.

Iterating over a ChainMap

We can iterate over a ChainMap like a normal dictionary using the .items() method.

A = { "pen" : 15, "eraser" : 22 }
B = { "marker" : 10, "eraser" : 40 }

chain = ChainMap(A, B)

for k,v in chain.items():
  print(k,":",v)

Output

marker : 10
eraser : 22
pen : 15

If a key occurs more than once, then only the first occurrence of the key is printed. In our example, eraser occurs two times but only the first occurrence is printed.

We can also get the list of keys and values using the .keys() and .values() methods respectively.

keys=list(chain.keys())
values=list(chain.values())

print("Keys : ",keys)
print("Values : ",values)

Output

Keys :  ['marker', 'pen', 'eraser']
Values :  [10, 15, 22]

Complexity Analysis for Chain Map

Time Complexity

Suppose there are N dictionaries with M keys. Then the time complexity for building a Chain map will be O(N) and the time complexity for retrieving a value will be O(N) in the worst case.

If you were to combine all the dictionaries using the .update() method, then the time complexity for building the final dictionary would be O(N*M) and the time complexity for retrieving would be O(1)

This makes chain map the perfect option if you are combining dictionaries often and retrieving rarely.

Space Complexity

The space complexity of chain map is O(N) since we are storing only the references to the N dictionaries.

Applications of Chain Map

  • A very common use case for Chain Map is when you need to access multiple dictionaries as a single dictionary.

For example, consider two dictionaries shirts and pants which store the prices of different types of shirts and pants.

shirts = { "short sleeve" : 500, "overshirt" : 1000, "t-shirt" : 100 }
pants = { "trousers" : 300, "chinos" : 150 } 

Now imagine if we have a shopping cart dictionary and a customer can add shirts and pants to it. We need to calculate the total price.

cart = { "t-shirt" : 3, "chinos" : 4 } 

chain = ChainMap(shirts, pants)

total = 0
for k,v in cart.items():
  total += chain[k] * v
 
# 3x100 + 4x150
print(total)

Output

900 

As we can see, we can calculate the total very easily using a chain map without worrying about whether the item in the cart is a shirt or pants.

  • Another very interesting application of chain map is when you need to prioritize some value over another. This is based on the fact that when a key is present more than once in a chain map, only the first occurrence is affected.

Imagine if you are building a DNS(Domain Name System) resolver application. A user can enter their own DNS Server IP address. If no IP address is entered then the default address of 8.8.8.8 should be used.

from collections import ChainMap

custom_dns={}   # The user does not provide a address
public_dns={"DNS":"208.67.222.222"}
global_dns={"DNS": "8.8.8.8"}

chain = ChainMap(custom_dns,public_dns,global_dns)
print(chain["DNS"])

Output

208.67.222.222

As we can see in the above example, if the user enters a DNS server then that address must be used else the next configuration should be used.

Practice Questions

  1. Import Chain Map from collections module and create a ChainMap object
  2. Reverse the ChainMap (Hint : use reversed() function)
  3. Simulate variable scope(local, global, external) using ChainMap

With this article at OpenGenus, you must have the complete idea of ChainMap in Python.

ChainMap in Python
Share this