Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Access nested dictionary items via a list of keys?

I have a complex dictionary structure which I would like to access via a list of keys to address the correct item.

dataDict = {     "a":{         "r": 1,         "s": 2,         "t": 3         },     "b":{         "u": 1,         "v": {             "x": 1,             "y": 2,             "z": 3         },         "w": 3         } }      maplist = ["a", "r"] 

or

maplist = ["b", "v", "y"] 

I have made the following code which works but I'm sure there is a better and more efficient way to do this if anyone has an idea.

# Get a given data from a dictionary with position provided as a list def getFromDict(dataDict, mapList):         for k in mapList: dataDict = dataDict[k]     return dataDict  # Set a given data in a dictionary with position provided as a list def setInDict(dataDict, mapList, value):      for k in mapList[:-1]: dataDict = dataDict[k]     dataDict[mapList[-1]] = value 
like image 507
kolergy Avatar asked Feb 04 '13 18:02

kolergy


People also ask

How do you access elements of nested dictionary?

To access element of a nested dictionary, we use indexing [] syntax in Python.

How do you get an item from a nested dictionary Python?

Access Values using get() Another way to access value(s) in a nested dictionary ( employees ) is to use the dict. get() method. This method returns the value for a specified key. If the specified key does not exist, the get() method returns None (preventing a KeyError ).


2 Answers

Use reduce() to traverse the dictionary:

from functools import reduce  # forward compatibility for Python 3 import operator  def getFromDict(dataDict, mapList):     return reduce(operator.getitem, mapList, dataDict) 

and reuse getFromDict to find the location to store the value for setInDict():

def setInDict(dataDict, mapList, value):     getFromDict(dataDict, mapList[:-1])[mapList[-1]] = value 

All but the last element in mapList is needed to find the 'parent' dictionary to add the value to, then use the last element to set the value to the right key.

Demo:

>>> getFromDict(dataDict, ["a", "r"]) 1 >>> getFromDict(dataDict, ["b", "v", "y"]) 2 >>> setInDict(dataDict, ["b", "v", "w"], 4) >>> import pprint >>> pprint.pprint(dataDict) {'a': {'r': 1, 's': 2, 't': 3},  'b': {'u': 1, 'v': {'w': 4, 'x': 1, 'y': 2, 'z': 3}, 'w': 3}} 

Note that the Python PEP8 style guide prescribes snake_case names for functions. The above works equally well for lists or a mix of dictionaries and lists, so the names should really be get_by_path() and set_by_path():

from functools import reduce  # forward compatibility for Python 3 import operator  def get_by_path(root, items):     """Access a nested object in root by item sequence."""     return reduce(operator.getitem, items, root)  def set_by_path(root, items, value):     """Set a value in a nested object in root by item sequence."""     get_by_path(root, items[:-1])[items[-1]] = value 

And for completion's sake, a function to delete a key:

def del_by_path(root, items):     """Delete a key-value in a nested object in root by item sequence."""     del get_by_path(root, items[:-1])[items[-1]] 
like image 168
Martijn Pieters Avatar answered Nov 08 '22 09:11

Martijn Pieters


It seems more pythonic to use a for loop. See the quote from What’s New In Python 3.0.

Removed reduce(). Use functools.reduce() if you really need it; however, 99 percent of the time an explicit for loop is more readable.

def nested_get(dic, keys):         for key in keys:         dic = dic[key]     return dic 

Note that the accepted solution doesn't set non-existing nested keys (it raises KeyError). Using the approach below will create non-existing nodes instead:

def nested_set(dic, keys, value):     for key in keys[:-1]:         dic = dic.setdefault(key, {})     dic[keys[-1]] = value 

The code works in both Python 2 and 3.

like image 21
DomTomCat Avatar answered Nov 08 '22 08:11

DomTomCat