У меня сложная структура словаря, к которой я хотел бы получить доступ через список ключей для адресации правильного элемента.
dataDict = {
"a":{
"r": 1,
"s": 2,
"t": 3
},
"b":{
"u": 1,
"v": {
"x": 1,
"y": 2,
"z": 3
},
"w": 3
}
}
maplist = ["a", "r"]
или
maplist = ["b", "v", "y"]
Я сделал следующий код, который работает, но я уверен, что есть лучший и более эффективный способ сделать это, если у кого-то есть идея.
# 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
python
list
dictionary
колерги
источник
источник
Ответы:
Используйте
reduce()
для просмотра словаря:from functools import reduce # forward compatibility for Python 3 import operator def getFromDict(dataDict, mapList): return reduce(operator.getitem, mapList, dataDict)
и повторно использовать,
getFromDict
чтобы найти место для хранения значенияsetInDict()
:def setInDict(dataDict, mapList, value): getFromDict(dataDict, mapList[:-1])[mapList[-1]] = value
Все, кроме последнего элемента,
mapList
необходимы, чтобы найти «родительский» словарь, в который нужно добавить значение, а затем использовать последний элемент, чтобы установить значение для правильного ключа.Демо:
>>> 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}}
Обратите внимание, что руководство по стилю Python PEP8 предписывает для функций имена snake_case . Вышеупомянутое одинаково хорошо работает со списками или смесью словарей и списков, поэтому имена действительно должны быть
get_by_path()
и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
И для завершения, функция для удаления ключа:
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]]
источник
try:
,except (KeyError, IndexError): return default_value
вокруг текущейreturn
строки.dict.setdefault()
а неdict.__getitem__
.Кажется более питоническим использовать
for
цикл. См. Цитату из статьи «Что нового в Python 3.0» .def nested_get(dic, keys): for key in keys: dic = dic[key] return dic
Обратите внимание, что принятое решение не устанавливает несуществующие вложенные ключи (оно возникает
KeyError
). При использовании следующего подхода вместо этого будут созданы несуществующие узлы:def nested_set(dic, keys, value): for key in keys[:-1]: dic = dic.setdefault(key, {}) dic[keys[-1]] = value
Код работает как в Python 2, так и в 3.
источник
getFromDict
они могут уничтожить вызывающийdataDict
. Я быcopy.deepcopy(dataDict)
первым. Конечно (как написано) такое поведение желательно во второй функции.getFromDict
само по себе не разрушает вызывающегоdataDict
? Это связано с изменением возвращаемого значения, которое было сделано вне функции. Пользователь всегда может сделать копию, если он не хочет этого, но нет возможности отменить копию, сделанную внутри функции, поэтому не копировать более гибко.Использование reduce - это разумно, но метод set OP может иметь проблемы, если родительские ключи не существуют заранее во вложенном словаре. Поскольку это первая публикация SO, которую я видел по этой теме в моем поиске в Google, я хотел бы сделать ее немного лучше.
Метод set в ( Установка значения во вложенном словаре Python с учетом списка индексов и значений ) кажется более устойчивым к отсутствию родительских ключей. Чтобы скопировать это:
def nested_set(dic, keys, value): for key in keys[:-1]: dic = dic.setdefault(key, {}) dic[keys[-1]] = value
Кроме того, может быть удобно иметь метод, который просматривает дерево ключей и получает все абсолютные пути ключей, для которых я создал:
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()], [])
Одно из его применений - преобразовать вложенное дерево в DataFrame pandas, используя следующий код (при условии, что все листы во вложенном словаре имеют одинаковую глубину).
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)
источник
nested_set
?Эта библиотека может быть полезной: https://github.com/akesterson/dpath-python
источник
Как насчет использования рекурсивных функций?
Чтобы получить значение:
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]
И чтобы установить значение:
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
источник
Решил это с помощью рекурсии:
def get(d,l): if len(l)==1: return d[l[0]] return get(d[l[0]],l[1:])
Используя ваш пример:
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
источник
def get(d,l, default_val=None): if l[0] not in d: return default_val elif len(l)==1: return d[l[0]] else: return get(d[l[0]],l[1:])
Вместо того, чтобы снижать производительность каждый раз, когда вы хотите найти значение, как насчет того, чтобы вы сгладили словарь один раз, а затем просто найдите ключ, например
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}
Таким образом, вы можете просто искать предметы, используя
flat_dict['b:v:y']
которые вы получите1
.И вместо того, чтобы просматривать словарь при каждом поиске, вы можете ускорить это, сгладив словарь и сохранив вывод, чтобы поиск из холодного запуска означал загрузку сглаженного словаря и простое выполнение поиска по ключу / значению без обход.
источник
Чистый стиль Python, без импорта:
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)
Выход
{'foo': {'bar': 'yay'}}
источник
Альтернативный способ, если вы не хотите вызывать ошибки при отсутствии одного из ключей (чтобы ваш основной код мог работать без перебоев):
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
В этом случае, если какой-либо из ключей ввода отсутствует, возвращается значение None, которое можно использовать в качестве проверки в вашем основном коде для выполнения альтернативной задачи.
источник
Приятно видеть эти ответы на наличие двух статических методов для установки и получения вложенных атрибутов. Эти решения намного лучше, чем использование вложенных деревьев https://gist.github.com/hrldcpr/2012250.
Вот моя реализация.
Использование :
Чтобы установить вызов вложенного атрибута
sattr(my_dict, 1, 2, 3, 5) is equal to my_dict[1][2][3][4]=5
Чтобы получить вызов вложенного атрибута
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(KeyError, TypeError): 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 type(d.get(attr)) is not dict: d[attr] = {} d = d[attr] d[attrs[-2]] = attrs[-1]
источник
Если вам также нужна возможность работать с произвольным json, включая вложенные списки и dicts, и хорошо обрабатывать недопустимые пути поиска, вот мое решение:
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
источник
Как насчет проверки, а затем установки элемента dict без обработки всех индексов дважды?
Решение:
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
Пример рабочего процесса:
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()
Контрольная работа
>>> 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()
источник
Очень поздно на вечеринку, но публикация на случай, если это может кому-то помочь в будущем. В моем случае лучше всего работала следующая функция. Работает для извлечения любого типа данных из словаря
dict - это словарь, содержащий наше значение
list - это список «шагов» к нашей ценности
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
источник
Вы можете использовать pydash:
import pydash as _ _.get(dataDict, ["b", "v", "y"], default='Default')
https://pydash.readthedocs.io/en/latest/api.html
источник
метод конкатенации строк:
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
источник
Расширяя подход @DomTomCat и других, эти функциональные (т. Е. Возвращают измененные данные через глубокую копию, не влияя на ввод) сеттер и сопоставитель работают для вложенных
dict
иlist
.сеттер:
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
картограф:
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
источник
Вы можете использовать эту
eval
функцию в Python.def nested_parse(nest, map_list): nestq = "nest['" + "']['".join(map_list) + "']" return eval(nestq, {'__builtins__':None}, {'nest':nest})
Объяснение
Для вашего примера запроса:
maplist = ["b", "v", "y"]
nestq
будет"nest['b']['v']['y']"
гдеnest
вложенный словарь.eval
Функция выполняет встроенный в данную строку. Однако важно быть осторожным в отношении возможных уязвимостей, возникающих в результате использованияeval
функции. Обсуждение можно найти здесь:В этой
nested_parse()
функции я убедился, что__builtins__
глобальные переменные недоступны, а доступная только локальная переменная являетсяnest
словарем.источник