Search code examples
pythondictionarykey-value

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


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.


Solution

  • 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)