Access nested dictionary items via a list of keys?
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]]
accessing nested dict from list of keys
One approach is the following:
def resolve_value(obj, keys):
for key in keys:
obj = obj[key]
return obj
k = ['l0', 'l1', 'l2']
d = {'l0': {'l1': {'l2': 'hello'}}}
print(resolve_value(d, k))
Output
hello
Can I access a nested dict with a list of keys?
This solution creates another dictionary with same keys and then updates the existing dictionary:
#!/usr/bin/env python
from six.moves import reduce
def update2(input_dictionary, new_value, loc):
"""
Update a dictionary by defining the keys.
Parameters
----------
input_dictionary : dict
new_value : object
loc : iterable
Location
Returns
-------
new_dict : dict
Examples
--------
>>> example = {'a': {'b': 'c'}, '1': {'2': {'3': {'4': '5'}}}}
>>> update2(example, 'new', ('a', 'b'))
{'a': {'b': 'new'}, '1': {'2': {'3': {'4': '5'}}}}
>>> update2(example, 'foo', ('1', '2', '3', '4'))
{'a': {'b': 'new'}, '1': {'2': {'3': {'4': 'foo'}}}}
>>> update2(example, 'bar', ('1', '2'))
{'a': {'b': 'new'}, '1': {'2': 'bar'}}
"""
new_dict = reduce(lambda x, y: {y: x}, reversed(loc), new_value)
input_dictionary.update(new_dict)
return input_dictionary
if __name__ == '__main__':
import doctest
doctest.testmod()
use string, list or tuple for access keys
Safe method to get value of nested dictionary
You could use get
twice:
example_dict.get('key1', {}).get('key2')
This will return None
if either key1
or key2
does not exist.
Note that this could still raise an AttributeError
if example_dict['key1']
exists but is not a dict (or a dict-like object with a get
method). The try..except
code you posted would raise a TypeError
instead if example_dict['key1']
is unsubscriptable.
Another difference is that the try...except
short-circuits immediately after the first missing key. The chain of get
calls does not.
If you wish to preserve the syntax, example_dict['key1']['key2']
but do not want it to ever raise KeyErrors, then you could use the Hasher recipe:
class Hasher(dict):
# https://stackoverflow.com/a/3405143/190597
def __missing__(self, key):
value = self[key] = type(self)()
return value
example_dict = Hasher()
print(example_dict['key1'])
# {}
print(example_dict['key1']['key2'])
# {}
print(type(example_dict['key1']['key2']))
# <class '__main__.Hasher'>
Note that this returns an empty Hasher when a key is missing.
Since Hasher
is a subclass of dict
you can use a Hasher in much the same way you could use a dict
. All the same methods and syntax is available, Hashers just treat missing keys differently.
You can convert a regular dict
into a Hasher
like this:
hasher = Hasher(example_dict)
and convert a Hasher
to a regular dict
just as easily:
regular_dict = dict(hasher)
Another alternative is to hide the ugliness in a helper function:
def safeget(dct, *keys):
for key in keys:
try:
dct = dct[key]
except KeyError:
return None
return dct
So the rest of your code can stay relatively readable:
safeget(example_dict, 'key1', 'key2')
How to access a specific key in a nested dictionary?
You can do it like so:
itemdetails = {101: { 'Item Name':'Tshirt', 'Price':150.00, 'Stock':20},
102: { 'Item Name':'Shirt', 'Price':300.00, 'Stock':20},
103: { 'Item Name':'Shorts', 'Price':450.00, 'Stock':20},
104: { 'Item Name':'Trousers', 'Price':750.00, 'Stock':20}}
option = str(input("Press [C] for Item Code , [N] for Item Name: "))
if option=='n':
itemname=(input("Enter Item name: "))
print(list(filter(lambda item: item['Item Name'] == itemname, itemdetails))[0])
else:
itemcode=int(input("Enter Item code: "))
print(itemdetails[itemcode])
How to access nested keys and their values to modify or delete all at once?
By nested keys and values, I assume you mean the ones under variants
. You can access those in much the same way as you iterated through items:
variant_list = []
for variant in item[variants]:
url = variant['url']
# and so on... for whatever other keys you're interested in
new_variant = {'url':url} # and whatever other keys you want
variant_list.append(new_variant)
I have to wonder, though, why you're reconstructing dictionaries that are similar to ones that JSON gave you? For many purposes, you might as well stick with the dictionary that JSON gave you.
Access nested dictionary without loops
You can use functools.reduce
.
>>> from functools import reduce
>>> mydict = {'a1':{'b1':1}, 'a2':2}
>>> keys = ['a1', 'b1']
>>> reduce(dict.get, keys, mydict)
1
dict.get
is a function that takes two arguments, the dict
and a key (and another optional argument not relevant here). mydict
is used as the initial value.
In case you ever need the intermediary results, use itertools.accumulate
.
>>> from itertools import accumulate
>>> list(accumulate([mydict] + keys, dict.get))
[{'a1': {'b1': 1}, 'a2': 2}, {'b1': 1}, 1]
Unfortunately, the function does not take an optional initializer
argument, so we are prepending mydict
to keys
.
Related Topics
Cqlsh Connection Error: 'Ref() Does Not Take Keyword Arguments'
Linux Command-Line Call Not Returning What It Should from Os.System
Are Dictionaries Ordered in Python 3.6+
How to Remove Duplicates from a List, While Preserving Order
Set Chrome Browser Binary Through Chromedriver in Python
Fail During Installation of Pillow (Python Module) in Linux
Get Total Physical Memory in Python
In Python Script, How to Set Pythonpath
Python Multiprocessing: Permission Denied
Django Server Killed Frequently
How to Install Pyodbc on Linux
Simulating Key Press Event Using Python For Linux
Show Matplotlib Plots (And Other Gui) in Ubuntu (Wsl1 & Wsl2)
Run Interactive Bash With Popen and a Dedicated Tty Python
How to Determine the Encoding of Text
What Exactly Do "U" and "R" String Prefixes Do, and What Are Raw String Literals
How to Download a File Over Http
Pandas: How to Merge Two Dataframes on a Column by Keeping the Information of the First One