5

I'm working on a Python script where I'm given lists of strings of the format: ['key1', 'key2', 'key2.key21.key211', 'key2.key22', 'key3'].

Each value in the list corresponds to an entry in a dictionary, and for entries structured like 'key2.key21.key211', they correspond to (in this example), a key 'key211' nested within 'key21', itself nested inside 'key2'.

The above list corresponds to the dictionary:

x = {
     'key1' : 'value1',
     'key2' : {
               'key21' : {
                          'key211': 'value211'
                         },
               'key22' : 'value22'
              },
     'key3' : 'value3'
    }

The names are not necessarily as regular as key(n)+; they can be of the form food.vegetables.potato, for example. The only guarantees I have is that the key names themselves, in the dictionary, do not contain the . character, and that the dictionary definitely contains all the entries referenced in the original list.

My question is, given such a list of strings, how do I programmatically access the corresponding entries in a dictionary? I can think of a solution using eval(), as well as one using just a traversal/search, but I want to avoid calls to eval(), and I get the impression that a traversal with comparisons would be slow (since dicts aren't search trees), and would entail a lot of nasty exception handling.

jonrsharpe
  • 115,751
  • 26
  • 228
  • 437
Jules
  • 14,200
  • 13
  • 56
  • 101
  • 1
    You should try something yourself and then edit the question to include the code you came up with. – quamrana Jun 04 '15 at 15:47
  • Let me see if I am reading your question correctly: what you need to do is split each key (string) on `'.'`, then access `original_dict[k[0]][k[1]][k[2]]...` for however many resulting k's there are? – Two-Bit Alchemist Jun 04 '15 at 15:47
  • @Two-Bit exactly that. quamrana I'll update with a tentative solution in about a half hour. – Jules Jun 04 '15 at 15:49
  • 1
    What do you mean *"search"*? You have the key for each level, so retrieving the value is an `O(1)` operation. – jonrsharpe Jun 04 '15 at 15:49
  • @jonrsharpe but python doesn't have facilities for accessing arbitrarily deep keys programmatically, afaik – Jules Jun 04 '15 at 15:51
  • 2
    There's no `dict` method for it, but it's trivial to write an iterative/recursive function to apply it. – jonrsharpe Jun 04 '15 at 15:51

1 Answers1

9

One approach is to write a function to access keys in nested dicts.

def deep_access(x,keylist):
     val = x
     for key in keylist:
         val = val[key]
     return val

s = 'key2.key21.key211'

print deep_access(x,s.split('.'))

result:

value211

Another approach, if you want to use similar syntax as a normal dictionary access you could subclass dict and override __getitem__ to allow for nested access when a tuple of keys is provided:

class NestedDict(dict):

    def __getitem__(self,keytuple):
        # if key is not a tuple then access as normal
        if not isinstance(keytuple, tuple):
            return super(NestedDict,self).__getitem__(keytuple)
        d = self
        for key in keytuple:
            d = d[key]
        return d

>>> nd = NestedDict(x)
>>> nd['key2']
{'key22': 'value22', 'key21': {'key211': 'value211'}}
>>> nd['key2','key22']
'value22'
>>> nd['key2','key21']
{'key211': 'value211'}
>>> nd['key2','key21','key211']
'value211'

You can then similarly implement __setitem__ and __delitem__ as needed.

Eric Appelt
  • 2,843
  • 15
  • 20