Установка значения во вложенном словаре python с учетом списка индексов и значений

Извините, если на этот вопрос был дан ответ - я искал решения, но, возможно, я не использую правильные условия поиска.

В любом случае, что я пытаюсь сделать, это программно установить значение в словаре, потенциально вложенном, с учетом списка индексов и значения.

Итак, допустим, например, мой список индексов:

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

и значение

'New York'

В результате я хочу, чтобы словарный объект вроде:

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

В принципе, список представляет собой "путь" во вложенный словарь.

Я думаю, что я могу сам построить словарь, но там, где я спотыкаюсь, как установить значение. Очевидно, если бы я просто писал код для этого вручную, это было бы:

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

Но как я могу индексировать в словаре и устанавливать значение как это программно, если у меня есть только список индексов и значение?

Надеюсь, это имеет смысл и не слишком глупый вопрос...:) Спасибо за любую помощь.

Ответ 1

Что-то вроде этого могло бы помочь:

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

И вы можете использовать его следующим образом:

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

Ответ 2

Во-первых, вы, вероятно, хотите посмотреть на setdefault

В качестве функции я бы написал ее как

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

Это будет использоваться как:

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

Это может быть очищено обработкой ошибок и т.д., Также может быть полезно использование *args а не один аргумент ключевого списка; но идея состоит в том, что вы можете перебирать ключи, подтягивая соответствующий словарь на каждом уровне.

Ответ 3

Вот еще один вариант:

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

Я изначально получил это отсюда: fooobar.com/questions/35882/....

Довольно умный и элегантный, если вы спросите меня.

Ответ 4

Вот мое простое решение: просто напишите

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

Вы даже можете сделать:

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

Теперь за кулисами:

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

Ответ 5

Я воспользовался свободой для продления кода из ответа Бакуриу. Поэтому оппоненты на это необязательны, поскольку его код сам по себе является остроумным решением, о котором я бы не подумал.

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

При настройке create_missing на True, вы должны установить только существующие значения:

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

Ответ 6

Используйте эти пары методов

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]