183 votes

Accéder à des éléments de dictionnaire imbriqués via une liste de clés ?

J'ai une structure de dictionnaire complexe à laquelle je voudrais accéder via une liste de clés pour adresser l'élément correct.

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

maplist = ["a", "r"]

ou

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

J'ai créé le code suivant qui fonctionne mais je suis sûr qu'il y a un moyen meilleur et plus efficace de le faire si quelqu'un a une idée.

# 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

305voto

Martijn Pieters Points 271458

Utilisez reduce() pour parcourir le dictionnaire :

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

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

et réutilisation getFromDict pour trouver l'emplacement où stocker la valeur de setInDict() :

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

Tous les éléments sauf le dernier dans mapList est nécessaire pour trouver le dictionnaire "parent" auquel ajouter la valeur, puis utiliser le dernier élément pour attribuer la valeur à la bonne clé.

Démonstration :

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

Notez que le guide de style Python PEP8 prescrit des noms en casse serpent pour les fonctions . Ce qui précède fonctionne aussi bien pour les listes ou un mélange de dictionnaires et de listes, donc les noms devraient vraiment être get_by_path() y set_by_path() :

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

Et pour terminer, une fonction pour supprimer une touche :

def del_by_path(root, items):
    """Delete a key-value in a nested object in root by item sequence."""
    del get_by_path(root, items[:-1])[items[-1]]

69voto

DomTomCat Points 3719

Il semble plus pythique d'utiliser un for boucle. Voir la citation de Nouveautés de Python 3.0 .

Supprimé reduce() . Utilisez functools.reduce() si vous en avez vraiment besoin ; cependant, dans 99 % des cas, une version explicite for La boucle est plus lisible.

def nested_get(dic, keys):    
    for key in keys:
        dic = dic[key]
    return dic

Notez que la solution acceptée ne fixe pas les clés imbriquées inexistantes (elle soulève KeyError ). L'utilisation de l'approche ci-dessous créera des nœuds inexistants à la place :

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

Le code fonctionne à la fois dans Python 2 et 3.

16voto

eafit Points 448

L'utilisation de reduce est intelligente, mais la méthode set du PO peut poser des problèmes si les clés parentales ne préexistent pas dans le dictionnaire imbriqué. Comme il s'agit du premier message du PO que j'ai vu pour ce sujet dans ma recherche sur Google, j'aimerais l'améliorer légèrement.

La méthode set dans ( Définition d'une valeur dans un dictionnaire python imbriqué à partir d'une liste d'indices et de valeurs ) semble plus robuste aux clés parentales manquantes. Pour le copier :

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

De plus, il peut être pratique d'avoir une méthode qui traverse l'arbre des clés et obtient tous les chemins de clés absolus, pour lesquels j'ai créé :

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()], [])

Une de ses utilisations consiste à convertir l'arbre imbriqué en un DataFrame pandas, en utilisant le code suivant (en supposant que toutes les feuilles du dictionnaire imbriqué ont la même profondeur).

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)

11voto

DMfll Points 104

Cette bibliothèque peut vous être utile : https://github.com/akesterson/dpath-python

Une bibliothèque python pour l'accès et la recherche dans les dictionnaires via /slashed/paths ala xpath

En gros, il vous permet d'utiliser un dictionnaire comme s'il s'agissait d'un système de fichiers.

2voto

OkezieE Points 373

Au lieu d'affecter les performances à chaque fois que vous voulez rechercher une valeur, pourquoi ne pas aplatir le dictionnaire une fois et rechercher simplement la clé comme suit b:v:y

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}

De cette façon, vous pouvez simplement rechercher des éléments en utilisant flat_dict['b:v:y'] ce qui vous donnera 1 .

Et au lieu de parcourir le dictionnaire à chaque consultation, vous pouvez accélérer le processus en aplatissant le dictionnaire et en sauvegardant la sortie de sorte qu'une consultation à partir d'un démarrage à froid signifie charger le dictionnaire aplati et simplement effectuer une consultation clé/valeur sans traversée.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X