web-dev-qa-db-fra.com

Définition de la valeur dans un dictionnaire python imbriqué à partir de la liste de l'index et de la valeur

Désolé si cette question a déjà reçu une réponse - je cherchais des solutions, mais je n'utilise peut-être pas les termes de recherche corrects.

Quoi qu'il en soit, ce que j'essaie de faire est de définir par programme une valeur dans un dictionnaire, potentiellement imbriquée, à partir d'une liste d'index et d'une valeur.

Ainsi, par exemple, supposons que ma liste d’indices soit:

['person', 'address', 'city'] 

et la valeur est

'New York'

Je veux par conséquent un objet dictionnaire comme:

{ 'Person': { 'address': { 'city': 'New York' } }

Fondamentalement, la liste représente un "chemin" dans un dictionnaire imbriqué.

Je pense que je peux construire le dictionnaire lui-même, mais ce qui me dérange, c'est comment définir la valeur. De toute évidence, si j’écrivais simplement du code pour cela manuellement, ce serait:

dict['Person']['address']['city'] = 'New York'

Mais comment puis-je indexer dans le dictionnaire et définir la valeur comme celle-ci par programme si je n'ai qu'une liste des index et la valeur?

J'espère que cela a du sens et n'est pas une question trop bête ... :) Merci pour toute aide.

26
peterk

Quelque chose comme ça pourrait aider:

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

Et vous pouvez l'utiliser comme ceci:

>>> d = {}
>>> nested_set(d, ['person', 'address', 'city'], 'New York')
>>> d
{'person': {'address': {'city': 'New York'}}}
40
Bakuriu

Tout d’abord, vous voudrez probablement regarder setdefault

En guise de fonction je l'écrirais comme

def get_leaf_dict( dict, key_list):
    res=dict
    for key in key_list:
        res=dict.setdefault( key, {} )
    return res

Ceci serait utilisé comme:

get_leaf_dict( dict, ['Person', 'address', 'city']) = 'New York'

Cela pourrait être nettoyé avec la gestion des erreurs et autres, en utilisant également *args plutôt qu'un seul argument de liste de clés pourrait être Nice; mais l'idée est que vous pouvez parcourir les clés, en tirant le dictionnaire approprié à chaque niveau.

4
Dave

Voici ma solution simple: il suffit d'écrire

terms = ['person', 'address', 'city'] 
result = nested_dict(3, str)
result[terms] = 'New York'  # as easy as it can be

Vous pouvez même faire:

terms = ['John', 'Tinkoff', '1094535332']  # account in Tinkoff Bank
result = nested_dict(3, float)
result[terms] += 2375.30

Maintenant les coulisses:

from collections import defaultdict


class nesteddict(defaultdict):
    def __getitem__(self, key):
        if isinstance(key, list):
            d = self
            for i in key:
                d = defaultdict.__getitem__(d, i)
            return d
        else:
            return defaultdict.__getitem__(self, key)
    def __setitem__(self, key, value):
        if isinstance(key, list):
            d = self[key[:-1]]
            defaultdict.__setitem__(d, key[-1], value)
        else:
            defaultdict.__setitem__(self, key, value)


def nested_dict(n, type):
    if n == 1:
        return nesteddict(type)
    else:
        return nesteddict(lambda: nested_dict(n-1, type))
2
Fancy John

Voici une autre option:

from collections import defaultdict
recursivedict = lambda: defaultdict(recursivedict)
mydict = recursivedict()

Je l’ai initialement obtenu à partir d’ici: https://stackoverflow.com/a/10218517/1530754 .

Assez intelligent et élégant si vous me demandez.

2
Ryan

J'ai pris la liberté d'étendre le code de la réponse de Bakuriu . Par conséquent, les votes positifs à ce sujet sont facultatifs, car son code est en soi une solution spirituelle, à laquelle je n'aurais pas pensé.

def nested_set(dic, keys, value, create_missing=True):
    d = dic
    for key in keys[:-1]:
        if key in d:
            d = d[key]
        Elif create_missing:
            d = d.setdefault(key, {})
        else:
            return dic
    if keys[-1] in d or create_missing:
        d[keys[-1]] = value
    return dic

Lorsque vous définissez create_missing sur True, vous vous assurez de ne définir que les valeurs existantes:

# Trying to set a value of a nonexistent key DOES NOT create a new value
print(nested_set({"A": {"B": 1}}, ["A", "8"], 2, False))
>>> {'A': {'B': 1}}

# Trying to set a value of an existent key DOES create a new value
print(nested_set({"A": {"B": 1}}, ["A", "8"], 2, True))
>>> {'A': {'B': 1, '8': 2}}

# Set the value of an existing key
print(nested_set({"A": {"B": 1}}, ["A", "B"], 2))
>>> {'A': {'B': 2}}
0
user3469861

Utilisez cette paire de méthodes

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

Voici une variante de la réponse de Bakuriu qui ne repose pas sur une fonction distincte:

keys = ['Person', 'address', 'city']
value = 'New York'

nested_dict = {}

# Build nested dictionary up until 2nd to last key
# (Effectively nested_dict['Person']['address'] = {})
sub_dict = nested_dict
for key_ind, key in enumerate(keys[:-1]):
    if not key_ind:
        # Point to newly added piece of dictionary
        sub_dict = nested_dict.setdefault(key, {})
    else:
        # Point to newly added piece of sub-dictionary
        # that is also added to original dictionary
        sub_dict = sub_dict.setdefault(key, {})
# Add value to last key of nested structure of keys 
# (Effectively nested_dict['Person']['address']['city'] = value)
sub_dict[keys[-1]] = value

print(nested_dict)

>>> {'Person': {'address': {'city': 'New York'}}}
0