How to delete dictionary values simply at a specific key 'path'?

Question:

I want to implement a function that:

Given a dictionary and an iterable of keys,

deletes the value accessed by iterating over those keys.

Originally I had tried

def delete_dictionary_value(dict, keys):

    inner_value = dict
    for key in keys:
        inner_value = inner_value[key]
    del inner_value
    return dict

Thinking that since inner_value is assigned to dict by reference, we can mutate dict implcitly by mutating inner_value. However, it seems that assigning inner_value itself creates a new reference (sys.getrefcount(dict[key]) is incremented by assigning inner_value inside the loop) – the result being that the local variable assignment is deled but dict is returned unchanged.

Using inner_value = None has the same effect – presumably because this merely reassigns inner_value.

Other people have posted looking for answers to questions like:

  • how do I ensure that my dictionary includes no values at the key x – which might be a question about recursion for nested dictionaries, or
  • how do I iterate over values at a given key (different flavours of this question)
  • how do I access the value of the key as opposed to the keyed value in a dictionary

This is none of the above – I want to remove a specific key,value pair in a dictionary that may be nested arbitrarily deeply – but I always know the path to the key,value pair I want to delete.

The solution I have hacked together so far is:

def delete_dictionary_value(dict, keys):

    base_str = f"del dict"
    property_access_str = ''.join([f"['{i}']" for i in keys])
    return exec(base_str + property_access_str)

Which doesn’t feel right.

This also seems like pretty basic functionality – but I’ve not found an obvious solution. Most likely I am missing something (most likely something blindingly obvious) – please help me see.

Asked By: zapl

||

Answers:

Don’t use a string-evaluation approach. Try to iteratively move to the last dictionary and delete the key-value pair from it. Here a possibility:

def delete_key(d, value_path):
    # move to most internal dictionary
    for kp in value_path[:-1]:
        if kp in dd and isinstance(d[kp], dict): 
            d = d[kp]
        else:
            e_msg = f"Key-value delete-operation failed at key '{kp}'"
            raise Exception(e_msg)

    # last entry check
    lst_kp = value_path[-1]
    if lst_kp not in d:
        e_msg = f"Key-value delete-operation failed at key '{lst_kp}'"
        raise Exception(e_msg)

    # delete key-value of most internal dictionary
    print(f'Value "{d[lst_kp]}" at position "{value_path}" deleted')
    del d[lst_kp]


d = {1: 2, 2:{3: "a"}, 4: {5: 6, 6:{8:9}}}

delete_key(d, [44, 6, 0])
#Value "9" at position "[4, 6, 8]" deleted
#{1: 2, 2: {3: 'a'}, 4: {5: 6, 6: {}}}
Answered By: cards

If error checking is not required at all, you just need to iterate to the penultimate key and then delete the value from there:

def del_by_path(d, keys):
  for k in keys[:-1]:
    d = d[k]
  return d.pop(keys[-1])

d = {'a': {'b': {'c': {'d': 'Value'}}}}
del_by_path(d, 'abcd')
# 'Value'
print(d)
# {'a': {'b': {'c': {}}}}

Just for fun, here’s a more "functional-style" way to do the same thing:

from functools import reduce
def del_by_path(d, keys):
    *init, last = keys
    return reduce(dict.get, init, d).pop(last)
Answered By: tzaman
Categories: questions Tags: , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.