简体   繁体   中英

python get pointer to an item in a nested dictionary/list combination based on a list of keys

I have a data structure that looks something like this:

someData = {"apple":{"taste":"not bad","colors":["red","yellow"]},
"banana":{"taste":"perfection","shape":"banana shaped"},
"some list":[6,5,3,2,4,6,7]}

and a list of keys which describes a path to some item in this structure

someList = ["apple","colors",2]

I already have a function getPath(path) (see below) that is supposed to return a pointer to the selected object. It works fine for reading, but I get into trouble when trying to write

print(getPath(someList))
>> yellow

getPath(someList) = "green"
>> SyntaxError: can't assign to function call

a = getPath(someList)
a = "green"
print(getPath(someList))
>> "yellow"

Is there a way to make this work? Maybe like this:

someFunc(someList, "green")
print(getPath(someList))
>> green

This question looks like this question , except that I want to write something to that item, and not just read it.
My actual data can be seen here (I used json.loads() to parse the data). Note that I plan on adding stuff to this structure. I want a general approach to future proof the project.

My code:

def getPath(path):
    nowSelection = jsonData
    for i in path:
        nowSelection = nowSelection[i]
    return nowSelection

The result you're getting from getPath() is the immutable value from a dict or list. This value does not even know it's stored in a dict or list, and there's nothing you can do to change it. You have to change the dict/list itself.

Example:

a = {'hello': [0, 1, 2], 'world': 2}
b = a['hello'][1]
b = 99             # a is completely unaffected by this

Compare with:

a = {'hello': [0, 1, 2], 'world': 2}
b = a['hello']     # b is a list, which you can change
b[1] = 99          # now a is {'hello': [0, 99, 2], 'world': 2}

In your case, instead of following the path all the way to the value you want, go all the way except the last step, and then modify the dict/list you get from the penultimate step:

getPath(["apple","colors",2]) = "green"  # doesn't work
getPath(["apple","colors"])[2] = "green" # should work

You could cache your getPath using custom caching function that allows you to manually populate saved cache.

from functools import wraps

def cached(func):
    func.cache = {}
    @wraps(func)
    def wrapper(*args):
        try:
            return func.cache[args]
        except KeyError:
            func.cache[args] = result = func(*args)
            return result   
    return wrapper


@cached
def getPath(l):
    ...

getPath.cache[(someList, )] = 'green'
getPath(someList)  # -> 'green'

You can't literally do what you're trying to do. I think the closest you could get is to pass the new value in, then manually reassign it within the function:

someData = {"apple":{"taste":"not bad","colors":["red","yellow"]}, "banana":{"taste":"perfection","shape":"banana shaped"}, "some list":[6,5,3,2,4,6,7]}

def setPath(path, newElement):
    nowSelection = someData
    for i in path[:-1]:  # Remove the last element of the path
        nowSelection = nowSelection[i]

    nowSelection[path[-1]] = newElement  # Then use the last element here to do a reassignment

someList = ["apple","colors",1]

setPath(someList, "green")

print(someData) 

{'apple': {'taste': 'not bad', 'colors': ['red', 'green']}, 'banana': {'taste': 'perfection', 'shape': 'banana shaped'}, 'some list': [6, 5, 3, 2, 4, 6, 7]}

I renamed it to setPath to reflect its purpose better.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM