10

I have a dictionary which contains dictionaries, which may also contain dictionaries, e.g.

dictionary = {'ID': 0001, 'Name': 'made up name', 'Transactions':
               {'Transaction Ref': 'a1', 'Transaction Details':
                  {'Bill To': 'abc', 'Ship To': 'def', 'Product': 'Widget A'
                      ...} ...} ... }

Currently I'm unpacking to get the 'Bill To' for ID 001, 'Transaction Ref' a1 as follows:

if dictionary['ID'] == 001:
    transactions = dictionary['Transactions']
        if transactions['Transaction Ref'] == 'a1':
            transaction_details = transactions['Transaction Details']
            bill_to = transaction_details['Bill To']

I can't help but think this is is a little clunky, especially the last two lines - I feel like something along the lines of the following should work:

bill_to = transactions['Transaction Details']['Bill To']

Is there a simpler approach for drilling down into nested dictionaries without having to unpack into interim variables?

wjandrea
  • 28,235
  • 9
  • 60
  • 81
user1530213
  • 111
  • 1
  • 2
  • 5

4 Answers4

20

You can use something like this:

>>> def lookup(dic, key, *keys):
...     if keys:
...         return lookup(dic.get(key, {}), *keys)
...     return dic.get(key)
...
>>> d = {'a':{'b':{'c':5}}}
>>> print lookup(d, 'a', 'b', 'c')
5
>>> print lookup(d, 'a', 'c')
None

Additionally, if you don't want to define your search keys as individual parameters, you can just pass them in as a list like this:

>>> print lookup(d, *['a', 'b', 'c'])
5
>>> print lookup(d, *['a', 'c'])
None
Rob.Kachmar
  • 2,129
  • 1
  • 18
  • 23
  • 1
    I hope you don't mind, I added an edit to clarify the true dynamic search capability of this function. The ability to pass in a list of search keys on-the-fly makes this a cut above the other techniques. – Rob.Kachmar Mar 09 '15 at 12:55
16
bill_to = transactions['Transaction Details']['Bill To']

actually works. transactions['Transaction Details'] is an expression denoting a dict, so you can do lookup in it. For practical programs, I would prefer an OO approach to nested dicts, though. collections.namedtuple is particularly useful for quickly setting up a bunch of classes that only contain data (and no behavior of their own).

There's one caveat: in some settings, you might want to catch KeyError when doing lookups, and in this setting, that works too, it's hard to tell which dictionary lookup failed:

try:
    bill_to = transactions['Transaction Details']['Bill To']
except KeyError:
    # which of the two lookups failed?
    # we don't know unless we inspect the exception;
    # but it's easier to do the lookup and error handling in two steps
Fred Foo
  • 355,277
  • 75
  • 744
  • 836
2

Following is another way of accessing nested dictionaries

>>> dbo={'m':{'d':{'v':{'version':1}}}}
>>> name='m__d__v__version' # it'll refer to 'dbo['m']['d']['v']['version']', '__' is the separator
>>> version = reduce(dict.get, name.split('__'), dbo)
>>> print version
1
>>>

Here, variable 'name' refers to 'dbo['m']['d']['v']['version']', which seems much shorter and neat.

This method will not throw KeyError. If a key is not found then you'll get 'None'.

Ref.: http://code.activestate.com/recipes/475156-using-reduce-to-access-deeply-nested-dictionaries/

blackfyre
  • 2,549
  • 5
  • 39
  • 58
  • It will throw a TypeError if you try name='m__foo__v__foo': TypeError: descriptor 'get' requires a 'dict' object but received a 'NoneType' – krasnaya Apr 04 '16 at 18:18
0

You can access nested dictionaries with a tuple using NestedDict.

>>> from ndicts.ndicts import NestedDict
>>> nested_dict = {"a": {"a": 0, "b": 1},
...                "b": {"a": 2, "b": 3}}
>>> nd = NestedDict(nested_dict)
>>> nd["a", "a"]
0
>>> nd["b", "a"]
2
>>> nd["a"]
{"a": 0, "b": 1}

To install ndicts:

pip install ndicts
edd313
  • 1,109
  • 7
  • 20