Dictionary Manipulation

Difficulty: Intermediate

Beyond basic lookups, Python dictionaries support a rich set of operations for modifying their contents. You can add new key-value pairs by simply assigning to a new key with dict[key] = value. If the key already exists, this overwrites the previous value. This dual behavior makes assignment both an insert and an update operation.

Removing entries can be done with the del statement (del dict[key]), which raises KeyError if the key does not exist, or with pop(key), which removes and returns the value. pop() accepts an optional default argument that is returned instead of raising KeyError when the key is missing. The popitem() method removes and returns the last inserted key-value pair (in Python 3.7+), which is useful for processing dictionaries as LIFO structures. The clear() method removes all entries.

The update() method merges another dictionary (or iterable of key-value pairs) into the current dictionary. Existing keys are overwritten by the incoming values. This is commonly used to apply defaults or merge configuration objects. In Python 3.9+, the | operator provides a more concise syntax for merging, and |= for in-place updates.

The setdefault() method is a powerful one-liner that returns the value for a key if it exists, or inserts the key with a given default value and returns that default. It is particularly useful for building dictionaries of lists, where you want to ensure a list exists before appending. This avoids the common pattern of checking for existence before insertion.

Nested dictionaries contain dictionaries as values, creating hierarchical data structures. Accessing nested values requires chaining square bracket lookups or get() calls. When modifying deeply nested structures, be careful about creating intermediate dictionaries. A common pattern is to use setdefault() to build nested structures incrementally.

Code examples

Adding, updating, and deleting keys

profile = {'name': 'Rahul', 'age': 25}

# Add new keys
profile['city'] = 'Delhi'
profile['skills'] = ['Python', 'SQL']
print('After adding:', profile)

# Update existing key
profile['age'] = 26
print('After update:', profile)

# Delete with del
del profile['skills']
print('After del:', profile)

# pop() returns the removed value
age = profile.pop('age')
print('Popped age:', age)
print('After pop:', profile)

# pop() with default for missing key
missing = profile.pop('email', 'not found')
print('Missing pop:', missing)

Assignment to a new key adds it; assignment to an existing key updates it. del raises KeyError for missing keys, while pop() can accept a default to avoid that. pop() also returns the removed value, which is useful when you need the value after removal.

update() for merging dictionaries

defaults = {'color': 'blue', 'size': 'medium', 'quantity': 1}
user_prefs = {'color': 'red', 'quantity': 5}

# update() modifies in place
config = defaults.copy()
config.update(user_prefs)
print('Merged:', config)

# update() with keyword arguments
config.update(size='large', weight=2.5)
print('After kwargs update:', config)

# Python 3.9+ merge operator
result = defaults | user_prefs
print('Merge operator:', result)

update() overwrites existing keys with new values and adds any new keys. It accepts another dict, an iterable of pairs, or keyword arguments. The | operator (Python 3.9+) creates a new merged dict without modifying either original.

setdefault() for safe initialization

# Group words by their first letter
words = ['apple', 'banana', 'avocado', 'blueberry', 'cherry', 'apricot']

grouped = {}
for word in words:
    first_letter = word[0]
    grouped.setdefault(first_letter, []).append(word)

for letter, group in grouped.items():
    print(f'{letter}: {group}')

# setdefault returns existing value if key exists
d = {'x': 10}
result = d.setdefault('x', 99)
print(f'\nExisting key: {result}')
result = d.setdefault('y', 99)
print(f'New key: {result}')
print(f'Dict: {d}')

setdefault() is a one-liner that checks if a key exists, inserts a default if not, and returns the value. It is ideal for building dicts of lists since it ensures the list is created before appending.

Nested dictionaries

# Nested dict structure
company = {
    'name': 'TechCorp',
    'departments': {
        'engineering': {
            'head': 'Alice',
            'team_size': 15
        },
        'marketing': {
            'head': 'Bob',
            'team_size': 8
        }
    }
}

# Accessing nested values
print('Company:', company['name'])
print('Eng head:', company['departments']['engineering']['head'])
print('Mkt size:', company['departments']['marketing']['team_size'])

# Safe nested access with get()
hr_head = company.get('departments', {}).get('hr', {}).get('head', 'Not assigned')
print('HR head:', hr_head)

# Modifying nested dicts
company['departments']['engineering']['team_size'] = 18
company['departments']['hr'] = {'head': 'Carol', 'team_size': 5}
print('Updated eng size:', company['departments']['engineering']['team_size'])
print('New HR dept:', company['departments']['hr'])

Nested dicts model hierarchical data. Chain [] or get() calls to access deep values. Chaining get() with empty dict defaults prevents KeyError at every level. You can add new nested dicts by assigning a dict to a new key.

Key points

Concepts covered

adding keys, updating values, del statement, pop(), update(), setdefault(), nested dicts