0

If I have a dictionary that is nested, and I pass in a string like "key1.key2.key3" which would translate to:

myDict["key1"]["key2"]["key3"]

What would be an elegant way to be able to have a method where I could pass on that string and it would translate to that key assignment? Something like myDict.set_nested('key1.key2.key3', someValue)

Jasonca1
  • 4,848
  • 6
  • 25
  • 42
  • I believe the following question can help you: https://stackoverflow.com/questions/1957780/how-to-override-the-operator-in-python – Itay Aug 19 '19 at 16:05
  • I did this. https://github.com/AndroxxTraxxon/cfgutils – David Culbreth Aug 19 '19 at 16:07
  • 1
    This seems like what you're trying to do: https://stackoverflow.com/questions/14692690/access-nested-dictionary-items-via-a-list-of-keys – anderw Aug 19 '19 at 16:16

3 Answers3

4

Using only builtin stuff:

def set(my_dict, key_string, value):
    """Given `foo`, 'key1.key2.key3', 'something', set foo['key1']['key2']['key3'] = 'something'"""

    # Start off pointing at the original dictionary that was passed in.
    here = my_dict

    # Turn the string of key names into a list of strings.
    keys = key_string.split(".")

    # For every key *before* the last one, we concentrate on navigating through the dictionary.
    for key in keys[:-1]:
        # Try to find here[key]. If it doesn't exist, create it with an empty dictionary. Then,
        # update our `here` pointer to refer to the thing we just found (or created).
        here = here.setdefault(key, {})

    # Finally, set the final key to the given value
    here[keys[-1]] = value


myDict = {}
set(myDict, "key1.key2.key3", "some_value")
assert myDict == {"key1": {"key2": {"key3": "some_value"}}}

This traverses myDict one key at a time, ensuring that each sub-key refers to a nested dictionary.

You could also solve this recursively, but then you risk RecursionError exceptions without any real benefit.

Kirk Strauser
  • 30,189
  • 5
  • 49
  • 65
1

There are a number of existing modules that will already do this, or something very much like it. For example, the jmespath module will resolve jmespath expressions, so given:

>>> mydict={'key1': {'key2': {'key3': 'value'}}}

You can run:

>>> import jmespath
>>> jmespath.search('key1.key2.key3', mydict)
'value'

The jsonpointer module does something similar, although it likes / for a separator instead of ..

Given the number of pre-existing modules I would avoid trying to write your own code to do this.

Community
  • 1
  • 1
larsks
  • 277,717
  • 41
  • 399
  • 399
0

EDIT: OP's clarification makes it clear that this answer isn't what he's looking for. I'm leaving it up here for people who find it by title.


I implemented a class that did this a while back... it should serve your purposes. I achieved this by overriding the default getattr/setattr functions for an object.

Check it out! AndroxxTraxxon/cfgutils

This lets you do some code like the following...

from cfgutils import obj

a = obj({
    "b": 123,
    "c": "apple",
    "d": {
        "e": "nested dictionary value"
    }
})

print(a.d.e)
>>> nested dictionary value
David Culbreth
  • 2,610
  • 16
  • 26