JustABeaver
JustABeaver

Reputation: 29

Sending python dictionary path as variable

this or similar to was maybe asked in the past but I couldn't find a solution to suit my case. I currently have this following dict:

my_dict = {
    "test": [
        {
            "inner_test": [
                {
                    "name": "Jack"
                },
                {
                    "name": "Ann"
                }
            ]
        },
        {
            "inner_test": [
                {
                    "name": "David"
                },
                {
                    "name": "Danny"
                }
            ],
            "some_test": [
                3,
                [
                    {
                        "name": "Benny"
                    },
                    {
                        "name": "Ann"
                    }
                ]
            ]
        }
    ]
}

I am using custom python libraries to get a path of a specific key (I don't know the nesting depth nor the place of the key), for example I want to get the path to the 1st 'Ann' value so the variable will contain

test_var = ['test',0,'inner_test',1,'name']

Next step, I want to add another key+value to the code as a sibling to the path I found earlier, for example -

test_var_2 = test_var[:-1]

So now test_var_2 = ['test',0,'inner_test',1]

My question is, how can I send test_var_2 as a path variable instead of sending

`my_dict['test',0,'inner_test',1]`

For example if I want to add another key,value pair like -

my_dict['test',0,'inner_test',1, 'country'] = 'a_country'

but send it like this:

my_dict[test_var_2] = 'a_country'

I tried converting test_var_2 to a tuple but I got a 'Key Error' In addition, I tried using this library - https://pypi.org/project/dict-deep/ to no avail as well. Thanks :)

Upvotes: 0

Views: 537

Answers (1)

wwii
wwii

Reputation: 23753

how can I send test_var_2 as a path variable

Write a function to iteratively work through the path.

# - this one is recursive
def f(vars,d):
    if not vars:
        return d
    d = d[vars[0]]
    return f(vars[1:],d)

# - "normal" iteration
def g(vars,d):
    vars = iter(vars)
    val = d[next(vars)]
    for item in vars:
        val = val[item]
    return val

>>>f(test_var_2,my_dict)
{'name': 'Ann'}

You could then make a partial function specific to the dictionary.

import functools
mydict_pathfinder = functools.partial(f,d=my_dict)
#mydict_pathfinder(test_var_2)

Or specific to the path through any similarly structured dictionary:

q = functools.partial(f,test_var_2)
#q(my_dict)

You could use operator.itemgetter to make a callable for each path segment then compose those callables. Producing a callable specific to the path.

import operator
path = [operator.itemgetter(item) for item in test_var_2]

def compose(funcs):
    def wrapped(d):
        func = funcs[0](d)
        for f in funcs[1:]:
            func = f(func)
        return func
    return wrapped

pathfinder = compose(path)
#pathfinder(my_dict)

add another key,value

Get the dict and update it.

>>> target = g(test_var_2,my_dict)
>>> target.update({'foo':'bar'})
>>> from pprint import pprint
>>> pprint(my_dict)
{'test': [{'inner_test': [{'name': 'Jack'}, {'foo': 'bar', 'name': 'Ann'}]},
          {'inner_test': [{'name': 'David'}, {'name': 'Danny'}],
           'some_test': [3, [{'name': 'Benny'}, {'name': 'Ann'}]]}]}

>>> g(test_var_2,my_dict).update({'another':'one'})
>>> pprint(my_dict)
{'test': [{'inner_test': [{'name': 'Jack'},
                          {'another': 'one', 'foo': 'bar', 'name': 'Ann'}]},
          {'inner_test': [{'name': 'David'}, {'name': 'Danny'}],
           'some_test': [3, [{'name': 'Benny'}, {'name': 'Ann'}]]}]}

>>> pathfinder(my_dict).update({999:000})
>>> pathfinder(my_dict).update({'x':'y'})
>>>pprint(my_dict)
{'test': [{'inner_test': [{'name': 'Jack'}, {999: 0, 'name': 'Ann', 'x': 'y'}]},
          {'inner_test': [{'name': 'David'}, {'name': 'Danny'}],
           'some_test': [3, [{'name': 'Benny'}, {'name': 'Ann'}]]}]}

If you want to use subscription - my_dict[test_var_2].update({'new':'item'}) - you will have to write a class with a __getitem__ method that iteratively walks the dictionary using a process similar to those shown above. Something like.

class Pathfinder:
    def __init__(self,d):
        self.d = d
    def __getitem__(self,item):
        # use any of the functions above
        return f(item)

pf = Pathfinder(my_dict)

>>> pf[test_var_2].update({'did it':'work?'})
>>> pprint(my_dict)
{'test': [{'inner_test': [{'name': 'Jack'},
                          {'did it': 'work?', 'name': 'Ann'}]},
          {'inner_test': [{'name': 'David'}, {'name': 'Danny'}],
           'some_test': [3, [{'name': 'Benny'}, {'name': 'Ann'}]]}]}

Upvotes: 1

Related Questions