it-swarm.com.de

Zugriff auf verschachtelte Wörterbücher über eine Liste von Schlüsseln?

Ich habe eine komplexe Wörterbuchstruktur, auf die ich über eine Liste von Schlüsseln zugreifen möchte, um das richtige Element anzusprechen.

dataDict = {
    "a":{
        "r": 1,
        "s": 2,
        "t": 3
        },
    "b":{
        "u": 1,
        "v": {
            "x": 1,
            "y": 2,
            "z": 3
        },
        "w": 3
        }
}    

maplist = ["a", "r"]

oder 

maplist = ["b", "v", "y"]

Ich habe den folgenden Code erstellt, der funktioniert, aber ich bin sicher, es gibt einen besseren und effizienteren Weg, dies zu tun, wenn jemand eine Idee hat.

# Get a given data from a dictionary with position provided as a list
def getFromDict(dataDict, mapList):    
    for k in mapList: dataDict = dataDict[k]
    return dataDict

# Set a given data in a dictionary with position provided as a list
def setInDict(dataDict, mapList, value): 
    for k in mapList[:-1]: dataDict = dataDict[k]
    dataDict[mapList[-1]] = value
106
kolergy

Verwenden Sie reduce(), um das Wörterbuch zu durchlaufen:

from functools import reduce  # forward compatibility for Python 3
import operator

def getFromDict(dataDict, mapList):
    return reduce(operator.getitem, mapList, dataDict)

und verwenden Sie getFromDict erneut, um den Ort zu finden, an dem der Wert für setInDict() gespeichert werden soll:

def setInDict(dataDict, mapList, value):
    getFromDict(dataDict, mapList[:-1])[mapList[-1]] = value

Alles außer dem letzten Element in mapList wird benötigt, um das 'übergeordnete' Wörterbuch zu finden, in das der Wert eingefügt werden soll, und dann mit dem letzten Element den Wert auf den rechten Schlüssel zu setzen.

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}}

Beachten Sie, dass der Python PEP8-Styleguide snake_case-Namen für Funktionen vorschreibt. Das Obige funktioniert gleichermaßen gut für Listen oder eine Mischung aus Wörterbüchern und Listen, daher sollten die Namen wirklich get_by_path() und set_by_path() lauten:

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
167
Martijn Pieters
  1. Die akzeptierte Lösung funktioniert nicht direkt für python3 - sie benötigt einen from functools import reduce
  2. Es scheint auch mehr Pythonic zu sein, eine for-Schleife zu verwenden. Siehe das Zitat aus Was ist neu in Python 3.0 .

    reduce() wurde entfernt. Verwenden Sie functools.reduce(), wenn Sie es wirklich brauchen. In 99 Prozent der Fälle ist eine explizite for-Schleife jedoch besser lesbar.

  3. Als nächstes legt die akzeptierte Lösung keine nicht vorhandenen verschachtelten Schlüssel fest (sie gibt eine KeyError zurück) - siehe die Antwort von @ eafit für eine Lösung

Warum also nicht die vorgeschlagene Methode aus der Frage von kolergy verwenden, um einen Wert zu erhalten:

def getFromDict(dataDict, mapList):    
    for k in mapList: dataDict = dataDict[k]
    return dataDict

Und der Code von @ eafit Antwort zum Setzen eines Wertes:

def nested_set(dic, keys, value):
    for key in keys[:-1]:
        dic = dic.setdefault(key, {})
    dic[keys[-1]] = value

Beide arbeiten direkt in Python 2 und 3

28
DomTomCat

Die Verwendung von verkleinern ist klug, aber die Set-Methode des OP kann zu Problemen führen, wenn die übergeordneten Schlüssel im verschachtelten Wörterbuch nicht vorhanden sind. Da dies der erste SO -Post ist, den ich in meiner Google-Suche zu diesem Thema gesehen habe, möchte ich es etwas besser machen.

Die set-Methode in ( Das Festlegen eines Werts in einem verschachtelten Python-Wörterbuch bei einer Liste von Indizes und Werten ) scheint robuster gegenüber fehlenden übergeordneten Schlüsseln. Um es zu kopieren:

def nested_set(dic, keys, value):
    for key in keys[:-1]:
        dic = dic.setdefault(key, {})
    dic[keys[-1]] = value

Es kann auch praktisch sein, über eine Methode zu verfügen, die den Schlüsselbaum durchläuft und alle absoluten Schlüsselpfade abruft, für die ich erstellt habe:

def keysInDict(dataDict, parent=[]):
    if not isinstance(dataDict, dict):
        return [Tuple(parent)]
    else:
        return reduce(list.__add__, 
            [keysInDict(v,parent+[k]) for k,v in dataDict.items()], [])

Eine Verwendung besteht darin, den verschachtelten Baum in einen Pandas-DataFrame zu konvertieren, indem der folgende Code verwendet wird (vorausgesetzt, dass alle Blätter im verschachtelten Wörterbuch dieselbe Tiefe haben).

def dict_to_df(dataDict):
    ret = []
    for k in keysInDict(dataDict):
        v = np.array( getFromDict(dataDict, k), )
        v = pd.DataFrame(v)
        v.columns = pd.MultiIndex.from_product(list(k) + [v.columns])
        ret.append(v)
    return reduce(pd.DataFrame.join, ret)
12
eafit

Diese Bibliothek kann hilfreich sein: https://github.com/akesterson/dpath-python

Eine Python-Bibliothek zum Zugreifen auf und Durchsuchen von Wörterbüchern über /slashed/pfade ala xpath

Im Grunde können Sie ein Wörterbuch mit einem Globus versehen, als wäre es ein Dateisystem.

8
DMfll

Python-Style ohne Import:

def nested_set(element, value, *keys):
    if type(element) is not dict:
        raise AttributeError('nested_set() expects dict as first argument.')
    if len(keys) < 2:
        raise AttributeError('nested_set() expects at least three arguments, not enough given.')

    _keys = keys[:-1]
    _element = element
    for key in _keys:
        _element = _element[key]
    _element[keys[-1]] = value

example = {"foo": { "bar": { "baz": "ok" } } }
keys = ['foo', 'bar']
nested_set(example, "yay", *keys)
print(example)

Ausgabe

{'foo': {'bar': 'yay'}}
1
Arount

Wie wäre es, wenn Sie das Dict-Element überprüfen und dann setzen, ohne alle Indizes zweimal zu bearbeiten?

Lösung:

def nested_yield(nested, keys_list):
    """
    Get current nested data by send(None) method. Allows change it to Value by calling send(Value) next time
    :param nested: list or dict of lists or dicts
    :param keys_list: list of indexes/keys
    """
    if not len(keys_list):  # assign to 1st level list
        if isinstance(nested, list):
            while True:
                nested[:] = yield nested
        else:
            raise IndexError('Only lists can take element without key')


    last_key = keys_list.pop()
    for key in keys_list:
        nested = nested[key]

    while True:
        try:
            nested[last_key] = yield nested[last_key]
        except IndexError as e:
            print('no index {} in {}'.format(last_key, nested))
            yield None

Beispielworkflow:

ny = nested_yield(nested_dict, nested_address)
data_element = ny.send(None)
if data_element:
    # process element
    ...
else:
    # extend/update nested data
    ny.send(new_data_element)
    ...
ny.close()

Prüfung

>>> cfg= {'Options': [[1,[0]],[2,[4,[8,16]]],[3,[9]]]}
    ny = nested_yield(cfg, ['Options',1,1,1])
    ny.send(None)
[8, 16]
>>> ny.send('Hello!')
'Hello!'
>>> cfg
{'Options': [[1, [0]], [2, [4, 'Hello!']], [3, [9]]]}
>>> ny.close()
1
And0k

Wie wäre es mit rekursiven Funktionen?

Um einen Wert zu erhalten:

def getFromDict(dataDict, maplist):
    first, rest = maplist[0], maplist[1:]

    if rest: 
        # if `rest` is not empty, run the function recursively
        return getFromDict(dataDict[first], rest)
    else:
        return dataDict[first]

Und einen Wert einstellen:

def setInDict(dataDict, maplist, value):
    first, rest = maplist[0], maplist[1:]

    if rest:
        try:
            if not isinstance(dataDict[first], dict):
                # if the key is not a dict, then make it a dict
                dataDict[first] = {}
        except KeyError:
            # if key doesn't exist, create one
            dataDict[first] = {}

        setInDict(dataDict[first], rest, value)
    else:
        dataDict[first] = value
1
xyres

Anstatt jedes Mal einen Performance-Treffer zu erzielen, wenn Sie einen Wert nachschlagen möchten, können Sie das Wörterbuch einmal abflachen und dann den Schlüssel wie b:v:y nachschlagen.

def flatten(mydict):
  new_dict = {}
  for key,value in mydict.items():
    if type(value) == dict:
      _dict = {':'.join([key, _key]):_value for _key, _value in flatten(value).items()}
      new_dict.update(_dict)
    else:
      new_dict[key]=value
  return new_dict

dataDict = {
"a":{
    "r": 1,
    "s": 2,
    "t": 3
    },
"b":{
    "u": 1,
    "v": {
        "x": 1,
        "y": 2,
        "z": 3
    },
    "w": 3
    }
}    

flat_dict = flatten(dataDict)
print flat_dict
{'b:w': 3, 'b:u': 1, 'b:v:y': 2, 'b:v:x': 1, 'b:v:z': 3, 'a:r': 1, 'a:s': 2, 'a:t': 3}

Auf diese Weise können Sie einfach mit flat_dict['b:v:y'] nach Artikeln suchen, die Ihnen 1 geben.

Anstatt das Wörterbuch bei jeder Suche zu durchlaufen, können Sie dies möglicherweise beschleunigen, indem Sie das Wörterbuch abflachen und die Ausgabe speichern, sodass bei einem Abruf beim Kaltstart das abgeflachte Wörterbuch geladen und einfach eine Schlüssel/Wert-Suche mit no ausgeführt werden muss Durchquerung.

1
OkezieE

Eine Alternative, wenn Sie keine Fehler melden möchten, wenn einer der Schlüssel fehlt (damit Ihr Hauptcode ohne Unterbrechung ausgeführt werden kann):

def get_value(self,your_dict,*keys):
    curr_dict_ = your_dict
    for k in keys:
        v = curr_dict.get(k,None)
        if v is None:
            break
        if isinstance(v,dict):
            curr_dict = v
    return v

In diesem Fall wird None zurückgegeben, wenn einer der Eingabetasten nicht vorhanden ist. Dies kann zur Überprüfung Ihres Hauptcodes verwendet werden, um eine alternative Aufgabe auszuführen.

1
Pulkit

eine Methode zum Verketten von Strings:

def get_sub_object_from_path(dict_name, map_list):
    for i in map_list:
        _string = "['%s']" % i
        dict_name += _string
    value = eval(dict_name)
    return value
#Sample:
_dict = {'new': 'person', 'time': {'for': 'one'}}
map_list = ['time', 'for']
print get_sub_object_from_path("_dict",map_list)
#Output:
#one
0
lucas

Wenn Sie auch mit beliebigem Json arbeiten möchten, einschließlich verschachtelter Listen und Dicts, und mit ungültigen Nachschlagewegen umgehen möchten, finden Sie hier meine Lösung:

from functools import reduce


def get_furthest(s, path):
    '''
    Gets the furthest value along a given key path in a subscriptable structure.

    subscriptable, list -> any
    :param s: the subscriptable structure to examine
    :param path: the lookup path to follow
    :return: a Tuple of the value at the furthest valid key, and whether the full path is valid
    '''

    def step_key(acc, key):
        s = acc[0]
        if isinstance(s, str):
            return (s, False)
        try:
            return (s[key], acc[1])
        except LookupError:
            return (s, False)

    return reduce(step_key, path, (s, True))


def get_val(s, path):
    val, successful = get_furthest(s, path)
    if successful:
        return val
    else:
        raise LookupError('Invalid lookup path: {}'.format(path))


def set_val(s, path, value):
    get_val(s, path[:-1])[path[-1]] = value
0
Grant Palmer

Sehr spät zur Party, aber wenn Sie etwas posten, kann dies in der Zukunft jemandem helfen. Für meinen Anwendungsfall hat die folgende Funktion am besten funktioniert. Funktioniert jeden Datentyp aus dem Wörterbuch

dict ist das Wörterbuch, das unseren Wert enthält

list ist eine Liste von "Schritten" zu unserem Wert

def getnestedvalue(dict, list):

    length = len(list)
    try:
        for depth, key in enumerate(list):
            if depth == length - 1:
                output = dict[key]
                return output
            dict = dict[key]
    except (KeyError, TypeError):
        return None

    return None
0
Jack Casey

In Erweiterung des Ansatzes von @DomTomCat und anderen funktionieren diese funktionalen Setter und Mapper für verschachtelte dict und list (dh sie geben geänderte Daten über Deepcopy zurück, ohne die Eingabe zu beeinflussen).

setter:

def set_at_path(data0, keys, value):
    data = deepcopy(data0)
    if len(keys)>1:
        if isinstance(data,dict):
            return {k:(set_by_path(v,keys[1:],value) if k==keys[0] else v) for k,v in data.items()}
        if isinstance(data,list):
            return [set_by_path(x[1],keys[1:],value) if x[0]==keys[0] else x[1] for x in enumerate(data)]
    else:
        data[keys[-1]]=value
        return data

mapper:

def map_at_path(data0, keys, f):
    data = deepcopy(data0)
    if len(keys)>1:
        if isinstance(data,dict):
            return {k:(map_at_path(v,keys[1:],f) if k==keys[0] else v) for k,v in data.items()}
        if isinstance(data,list):
            return [map_at_path(x[1],keys[1:],f) if x[0]==keys[0] else x[1] for x in enumerate(data)]
    else:
        data[keys[-1]]=f(data[keys[-1]])
        return data
0
alancalvitti

Mit Rekursion gelöst:

def get(d,l):
    if len(l)==1: return d[l[0]]
    return get(d[l[0]],l[1:])

An deinem Beispiel:

dataDict = {
    "a":{
        "r": 1,
        "s": 2,
        "t": 3
        },
    "b":{
        "u": 1,
        "v": {
            "x": 1,
            "y": 2,
            "z": 3
        },
        "w": 3
        }
}
maplist1 = ["a", "r"]
maplist2 = ["b", "v", "y"]
print(get(dataDict, maplist1)) # 1
print(get(dataDict, maplist2)) # 2
0
Poh Zi How

Es ist befriedigend, diese Antworten zu sehen, weil es zwei statische Methoden zum Setzen und Abrufen geschachtelter Attribute gibt. Diese Lösungen sind viel besser als die Verwendung geschachtelter Bäume https://Gist.github.com/hrldcpr/2012250

Hier ist meine Implementierung.

Verwendungszweck:

So legen Sie den Aufruf des verschachtelten Attributs sattr(my_dict, 1, 2, 3, 5) is equal to my_dict[1][2][3][4]=5 fest

So rufen Sie einen verschachtelten Attributaufruf auf gattr(my_dict, 1, 2)

def gattr(d, *attrs):
    """
    This method receives a dict and list of attributes to return the innermost value of the give dict       
    """
    try:
        for at in attrs:
            d = d[at]
        return d
    except:
        return None


def sattr(d, *attrs):
    """
    Adds "val" to dict in the hierarchy mentioned via *attrs
    For ex:
    sattr(animals, "cat", "leg","fingers", 4) is equivalent to animals["cat"]["leg"]["fingers"]=4
    This method creates necessary objects until it reaches the final depth
    This behaviour is also known as autovivification and plenty of implementation are around
    This implementation addresses the corner case of replacing existing primitives
    https://Gist.github.com/hrldcpr/2012250#gistcomment-1779319
    """
    for attr in attrs[:-2]:
        # If such key is not found or the value is primitive supply an empty dict
        if d.get(attr) is None or isinstance(d.get(attr), dict):
            d[attr] = {}
        d = d[attr]
    d[attrs[-2]] = attrs[-1]
0
nehemiah