Python Nested Dictionary

A nested dictionary is a dictionary where each key can map to another dictionary rather than a simple value like an integer or string. This can be extended to multiple levels, creating a hierarchy of dictionaries. A basic nested dictionary looks like this:

nested_dict = {'key1': {'subkey1': 'value1', 'subkey2': 'value2'},
               'key2': {'subkey1': 'value3', 'subkey2': 'value4'}}

Nested dictionaries excel at representing data with multiple layers of organization. Some common use cases include:

  • Employee Records: Organize employees by department, with each employee’s details in a nested dictionary.
  • Inventory Systems: Track product categories, subcategories, and individual product details.
  • JSON Data: Nested dictionaries naturally map to the structure of JSON data, making them essential for working with APIs and web services.

In this tutorial, we’ll explore how to create, access, and manipulate nested dictionaries in Python.

Creating a Nested Dictionary

Direct Creation

The most straightforward way to create a nested dictionary is to specify dictionaries as the values for the keys within curly braces.

Let’s create a nested dictionary to store employee records:

D = {'emp1': {'name': 'Bob', 'job': 'Mgr'},
     'emp2': {'name': 'Kim', 'job': 'Dev'},
     'emp3': {'name': 'Sam', 'job': 'Dev'}}

Using dict() Constructor

You can also create a nested dictionary using the dict() constructor. Simply provide the key-value pairs as keyword arguments to dict() function.

D = dict(emp1 = {'name': 'Bob', 'job': 'Mgr'},
         emp2 = {'name': 'Kim', 'job': 'Dev'},
         emp3 = {'name': 'Sam', 'job': 'Dev'})

print(D)
# Output: {'emp1': {'name': 'Bob', 'job': 'Mgr'},
#          'emp2': {'name': 'Kim', 'job': 'Dev'},
#          'emp3': {'name': 'Sam', 'job': 'Dev'}}

This method is useful if you have the key-value pairs ready. However, if you have separate lists for keys (e.g., employee IDs) and values (e.g., dictionaries of employee info), you can combine them using zip() and pass them to dict().

IDs = ['emp1','emp2','emp3']

EmpInfo = [{'name': 'Bob', 'job': 'Mgr'},
           {'name': 'Kim', 'job': 'Dev'},
           {'name': 'Sam', 'job': 'Dev'}]

D = dict(zip(IDs, EmpInfo))

print(D)
# Output: {'emp1': {'name': 'Bob', 'job': 'Mgr'},
#          'emp2': {'name': 'Kim', 'job': 'Dev'},
#          'emp3': {'name': 'Sam', 'job': 'Dev'}}

Here, zip() combines the IDs and EmpInfo lists, creating pairs that dict() then converts into a nested dictionary.

Creating a Nested Dictionary with Default Values

Sometimes, you might need a nested dictionary with default values. In such cases, use the fromkeys() method. This method takes an iterable of keys and a default value, which is a dictionary in our case. The result is a nested dictionary with the given keys and the specified default dictionary as the value for each key.

IDs = ['emp1','emp2','emp3']
Defaults = {'name': '', 'job': ''}

D = dict.fromkeys(IDs, Defaults)

print(D)
# Output: {'emp1': {'name': '', 'job': ''},
#          'emp2': {'name': '', 'job': ''},
#          'emp3': {'name': '', 'job': ''}}

This generates a nested dictionary D where the keys are employee IDs, and each value is a dictionary containing empty strings for ‘name’ and ‘job’.

Accessing Elements in a Nested Dictionary

You can access elements within a nested dictionary by specifying multiple keys in a chain, using square brackets []. Each key represents a level of nesting.

D = {'emp1': {'name': 'Bob', 'job': 'Mgr'},
     'emp2': {'name': 'Kim', 'job': 'Dev'},
     'emp3': {'name': 'Sam', 'job': 'Dev'}}

print(D['emp1']['name'])
# Output: Bob
print(D['emp2']['job'])
# Output: Dev

Attempting to access a key that does not exist within the nested dictionary will raise a KeyError.

print(D['emp1']['salary'])
# KeyError: 'salary'

To prevent this exception, you can use the get() method. This method returns the value for key if key is in the dictionary, else None, so that this method never raises a KeyError.

# key present
print(D['emp1'].get('name'))
# Output: Bob

# key absent
print(D['emp1'].get('salary'))
# Output: None

Modifying Elements in a Nested Dictionary

Modifying elements within a nested dictionary is straightforward. Just as you use keys to access specific values, you can also use them to change those values.

D = {'emp1': {'name': 'Bob', 'job': 'Mgr'},
     'emp2': {'name': 'Kim', 'job': 'Dev'},
     'emp3': {'name': 'Sam', 'job': 'Dev'}}

D['emp3']['name'] = 'Max'
D['emp3']['job'] = 'Janitor'

print(D['emp3'])
# Output: {'name': 'Max', 'job': 'Janitor'}

Adding Elements to a Nested Dictionary

You can easily add new keys at any level of nesting by directly assigning a value to the new key.

D = {'emp1': {'name': 'Bob', 'job': 'Dev'}}

# Add a new key-value pair to the nested dictionary
D['emp1']['salary'] = 50000

# Add a completely new employee (nested dictionary)
D['emp2'] = {'name': 'Kim', 'job': 'Dev', 'salary': 60000}

print(D)
# Output: {'emp1': {'name': 'Bob', 'job': 'Dev', 'salary': 50000},
#          'emp2': {'name': 'Kim', 'job': 'Dev', 'salary': 60000}}

Merging Nested Dictionaries

Merging nested dictionaries can be done in two ways, shallow merging and deep merging, depending on the desired outcome.

Shallow Merge

Shallow merging is a straightforward approach where the update() method is used. This method simply adds key-value pairs from one dictionary to another. For example:

D1 = {'emp1': {'name': 'Bob', 'job': 'Dev', 'salary': 50000}}
D2 = {'emp2': {'name': 'Kim', 'job': 'Dev', 'salary': 60000}}
D1.update(D2)
print(D1)
# Output: {'emp1': {'name': 'Bob', 'job': 'Dev', 'salary': 50000},
#          'emp2': {'name': 'Kim', 'job': 'Dev', 'salary': 60000}}

However, it is important to note that this is a shallow merge, meaning that if there are overlapping keys, the values from the second dictionary will overwrite the values in the first.

D1 = {'emp1': {'name': 'Bob', 'job': 'Dev'}}
D2 = {'emp1': {'salary': 50000},
      'emp2': {'name': 'Kim', 'job': 'Dev', 'salary': 60000}}
D1.update(D2)
print(D1)
# Output: {'emp1': {'salary': 50000},
#          'emp2': {'name': 'Kim', 'job': 'Dev', 'salary': 60000}}

Notice how the ‘name’ and ‘job’ keys from D1 are lost in the merged dictionary because they were overwritten by the ‘salary’ key from D2.

Deep Merge

To overcome this, we can employ deep merging. This method recursively merges dictionaries at all levels, ensuring that values are combined rather than overwritten. A common way to implement deep merging is to use a recursive function like this:

import collections.abc

def deep_update(source, updates):
    for key, value in updates.items():
        if isinstance(value, collections.abc.Mapping):
            source[key] = deep_update(source.get(key, {}), value)
        else:
            source[key] = value
    return source

D1 = {'emp1': {'name': 'Bob', 'job': 'Dev'}}
D2 = {'emp1': {'salary': 50000},
      'emp2': {'name': 'Kim', 'job': 'Dev', 'salary': 60000}}
deep_update(D1, D2)
print(D1)
# Output: {'emp1': {'name': 'Bob', 'job': 'Dev', 'salary': 50000},
#          'emp2': {'name': 'Kim', 'job': 'Dev', 'salary': 60000}}

In this example, the deep_update function iterates through the keys and values of the second dictionary. If a value is itself a dictionary (a collections.abc.Mapping), the function recursively calls itself to merge the nested dictionaries. Otherwise, it simply updates the value in the source dictionary. This results in a complete merging of the dictionaries at all levels.

Removing Nested Dictionary Elements

There are several ways to remove elements from a nested dictionary.

Remove an Item by Key

If you know the key of the item you want to remove, the pop() method is a convenient tool. It not only removes the key-value pair but also returns the removed value.

D = {'emp1': {'name': 'Bob', 'job': 'Mgr'},
     'emp2': {'name': 'Kim', 'job': 'Dev'},
     'emp3': {'name': 'Sam', 'job': 'Dev'}}

x = D.pop('emp3')

print(D)
# Output: {'emp1': {'name': 'Bob', 'job': 'Mgr'},
#         'emp2': {'name': 'Kim', 'job': 'Dev'}}

# get removed value
print(x)
# Output: {'name': 'Sam', 'job': 'Dev'}

If you don’t need the removed value, the del statement can be used. It simply removes the key-value pair based on the specified key.

D = {'emp1': {'name': 'Bob', 'job': 'Mgr'},
     'emp2': {'name': 'Kim', 'job': 'Dev'},
     'emp3': {'name': 'Sam', 'job': 'Dev'}}

del D['emp3']

print(D)
# Output: {'emp1': {'name': 'Bob', 'job': 'Mgr'},
#          'emp2': {'name': 'Kim', 'job': 'Dev'}}

Remove Last Inserted Item

In cases where you want to remove the last inserted item, the popitem() method comes in handy. It removes and returns the last inserted item as a tuple containing the key and value.

D = {'emp1': {'name': 'Bob', 'job': 'Mgr'},
     'emp2': {'name': 'Kim', 'job': 'Dev'},
     'emp3': {'name': 'Sam', 'job': 'Dev'}}

x = D.popitem()

print(D)
# Output: {'emp1': {'name': 'Bob', 'job': 'Mgr'},
#          'emp2': {'name': 'Kim', 'job': 'Dev'}}

# get removed pair
print(x)
# Output: ('emp3', {'name': 'Sam', 'job': 'Dev'})

It’s important to note that in Python versions before 3.7, the behavior of popitem() was to remove a random item, not necessarily the last inserted one.

Iterating Through a Nested Dictionary

You can iterate through a nested dictionary using nested loops. Here’s an example of iterating through both the outer and inner dictionaries:

D = {'emp1': {'name': 'Bob', 'job': 'Mgr'},
     'emp2': {'name': 'Kim', 'job': 'Dev'},
     'emp3': {'name': 'Sam', 'job': 'Dev'}}

for id, info in D.items():
    print("\nEmployee ID:", id)
    for key in info:
        print(key + ':', info[key])

# Output: Employee ID: emp1
#        name: Bob
#        job: Mgr

#        Employee ID: emp2
#        name: Kim
#        job: Dev

#        Employee ID: emp3
#        name: Sam
#        job: Dev

In this example, the outer loop iterates over each item in the main dictionary D, while the inner loop iterates over each key-value pair within the nested dictionary info.

Checking for Existence of Keys

To verify if a specific key exists within a nested dictionary, you can utilize the in operator. This operator allows you to check for the presence of a key at any level of the nested structure.

D = {'emp1': {'name': 'Bob', 'job': 'Mgr'},
     'emp2': {'name': 'Kim', 'job': 'Dev'},
     'emp3': {'name': 'Sam', 'job': 'Dev'}}

print('emp2' in D)              # Output: True
print('name' in D['emp2'])      # Output: True