Я создал следующий код, который работает, но я уверен, что есть лучший и более эффективный способ сделать это, если у кого-нибудь есть идея.
# Get a given data from a dictionary with position provided as a list defgetFromDict(dataDict, mapList): for k in mapList: dataDict = dataDict[k] return dataDict
# Set a given data in a dictionary with position provided as a list defsetInDict(dataDict, mapList, value): for k in mapList[:-1]: dataDict = dataDict[k] dataDict[mapList[-1]] = value
Переведено автоматически
Ответ 1
Используйте reduce() для обхода словаря:
from functools import reduce # forward compatibility for Python 3 import operator
и повторное использование getFromDict, чтобы найти место для хранения значения для setInDict():
defsetInDict(dataDict, mapList, value): getFromDict(dataDict, mapList[:-1])[mapList[-1]] = value
Все элементы, кроме последнего, в mapList необходимы, чтобы найти "родительский" словарь для добавления значения, затем используйте последний элемент, чтобы присвоить значение нужному ключу.
Обратите внимание, что руководство по стилю Python PEP8 предписывает имена snake_case для функций. Вышесказанное одинаково хорошо работает для списков или сочетания словарей и списков, поэтому имена действительно должны быть get_by_path() и set_by_path():
from functools import reduce # forward compatibility for Python 3 import operator
defget_by_path(root, items): """Access a nested object in root by item sequence.""" return reduce(operator.getitem, items, root)
defset_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
И для завершения, функция для удаления ключа:
defdel_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]]
Ответ 2
Использование for цикла кажется более питоническим. Смотрите Цитату из Что нового в Python 3.0 .
Удаленоreduce(). Используйте, functools.reduce() если вам это действительно нужно; однако в 99 процентах случаев явный for цикл более удобочитаем.
defnested_get(dic, keys): for key in keys: dic = dic[key] return dic
defnested_set(dic, keys, value): for key in keys[:-1]: dic = dic.setdefault(key, {}) dic[keys[-1]] = value
defnested_del(dic, keys): for key in keys[:-1]: dic = dic[key] del dic[keys[-1]]
Обратите внимание, что принятое решение не устанавливает несуществующие вложенные ключи (оно вызывает KeyError). Использование описанного выше подхода вместо этого создаст несуществующие узлы.
Код работает как на Python 2, так и на Python 3.
Ответ 3
Использование reduce является разумным, но у метода set OP могут возникнуть проблемы, если родительские ключи предварительно не существуют во вложенном словаре. Поскольку это первый пост SO, который я увидел по этой теме в своем поиске Google, я хотел бы сделать его немного лучше.
defnested_set(dic, keys, value): for key in keys[:-1]: dic = dic.setdefault(key, {}) dic[keys[-1]] = value
Кроме того, может быть удобно иметь метод, который обходит дерево ключей и получает все абсолютные пути к ключам, для которых я создал:
defkeysInDict(dataDict, parent=[]): ifnotisinstance(dataDict, dict): return [tuple(parent)] else: return reduce(list.__add__, [keysInDict(v,parent+[k]) for k,v in dataDict.items()], [])
Одним из способов его использования является преобразование вложенного дерева в фрейм данных pandas, используя следующий код (при условии, что все листы во вложенном словаре имеют одинаковую глубину).
defdict_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)