6

Is there some simple way to access nested dictionary key when at first you don't know which key you will be accessing?

For example:

dct = {'label': 'A', 'config': {'value': 'val1'}}

In this dictionary I will need to access either label key or value key inside another dict that is accessible through config key.

It depends on state.

For example if we have variable called label, so if:

label = True
if label:
   key = 'label'

in this case its kind of easy:

dct[key]

Now if label is false and I need to access nested dictionary, how can I dynamically specify it so I would not need to use ifs on every iterated item (I mean check everytime if label is used instead of value, because I will know that before starting iteration on dictionary full of dct dictionaries)?

like:

label = False
if label:
   key = 'label'
else:
    key = 'config..?' # it should be something like ['config']['value']
Gonçalo Peres
  • 11,752
  • 3
  • 54
  • 83
Andrius
  • 19,658
  • 37
  • 143
  • 243
  • You can write own dictionary class and overwrite `__getitem__` method to work with `dict['config.value']` - http://stackoverflow.com/questions/2390827/how-to-properly-subclass-dict-and-override-getitem-setitem – furas Oct 02 '16 at 15:50
  • 1
    or just make `key` a function e.g. `key = lambda d: d['config']['value']`. – kennytm Oct 02 '16 at 16:55
  • @kennytm Well actually it does not work that easily, cause it makes it as a function, and expects me to call that method, like `dct[key(dct)]`, which does not solve the problem, because then I need to call that key differently – Andrius Oct 02 '16 at 17:20
  • @Andrius If you make it a function, you call it as `result = key(dct)` in both cases, never `result = dct[key(dct)]`... – kennytm Oct 02 '16 at 17:24

3 Answers3

7

Expanding on @Barun's work, and possibly helping answer @Bhavani's question re setting values in a nested dictionary, here is a generalised solution to dynamically accessing or setting nested dictionary keys. Python 3.7.

from typing import List

class DynamicAccessNestedDict:
    """Dynamically get/set nested dictionary keys of 'data' dict"""

    def __init__(self, data: dict):
        self.data = data

    def getval(self, keys: List):
        data = self.data
        for k in keys:
            data = data[k]
        return data

    def setval(self, keys: List, val) -> None:
        data = self.data
        lastkey = keys[-1]
        for k in keys[:-1]:  # when assigning drill down to *second* last key
            data = data[k]
        data[lastkey] = val

You just wrap your dictionary in an instance of this class, then get and set by passing a list of keys.

dct = {'label': 'A', 'config': {'value': 'val1'}}

d = DynamicAccessNestedDict(dct)
assert d.getval(["label"]) == "A"
assert d.getval(["config", "value"]) == "val1"

# Set some new values
d.setval(["label"], "B")
d.setval(["config", "value"], "val2")

assert d.getval(["label"]) == "B"
assert d.getval(["config", "value"]) == "val2"
abulka
  • 1,316
  • 13
  • 18
4

If you know the key to be traversed, you can try out the following. This would work for any level of nested dicts.

dct = {'label': 'A', 'config': {'value': 'val1'}}

label = True
key = ('label',)
if not label:
    key = ('config', 'value')

ret = dct
for k in key:
  ret = ret[k]

print ret
Barun Sharma
  • 1,452
  • 2
  • 15
  • 20
  • In this case how to update the values of "label" and "value" keys. If "label" is passed as an argument then the value of "label" should be updated and if "config" and "value" are passed as arguments then the respective value should be updated. – Bhavani Prasad Jul 02 '19 at 14:55
1
from functools import reduce
import operator
#db is dictionary which need to update
#keys is a list need to depth of keys in a order
#value targeted value to update in dictionary 
class Dict_update:
    def set_by_path(self, db, keys, value):
        """Set a value in a nested object in db by keys sequence."""
        for index in range(1,len(keys)):
            subitem = self.get_by_path(db, keys[:index])
            if not isinstance(subitem, dict):
                self.get_by_path(db, keys[:index][:-1])[keys[:index][-1]] = {}
        self.get_by_path(db, keys[:-1])[keys[-1]] = value
        return db

    def get_by_path(self, db, keys):

        try: return reduce(operator.getitem, keys, db)
        except Exception as e:
            return None