Dostęp do zagnieżdżonych pozycji słownika za pomocą listy kluczy?
Mam złożoną strukturę słownika, do której chciałbym uzyskać dostęp poprzez listę kluczy, aby zaadresować poprawną pozycję.
dataDict = {
"a":{
"r": 1,
"s": 2,
"t": 3
},
"b":{
"u": 1,
"v": {
"x": 1,
"y": 2,
"z": 3
},
"w": 3
}
}
maplist = ["a", "r"]
Lub
maplist = ["b", "v", "y"]
Zrobiłem następujący kod, który działa, ale jestem pewien, że jest lepszy i bardziej skuteczny sposób, aby to zrobić, jeśli ktoś ma pomysł.
# 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
11 answers
Użyj reduce()
aby przejść do słownika:
from functools import reduce # forward compatibility for Python 3
import operator
def getFromDict(dataDict, mapList):
return reduce(operator.getitem, mapList, dataDict)
I ponownie użyj getFromDict
, aby znaleźć lokalizację do przechowywania wartości dla setInDict()
:
def setInDict(dataDict, mapList, value):
getFromDict(dataDict, mapList[:-1])[mapList[-1]] = value
Wszystko oprócz ostatniego elementu w {[7] } jest potrzebne, aby znaleźć słownik 'rodzica', aby dodać wartość do, a następnie użyć ostatniego elementu, aby ustawić wartość do prawego klawisza.
Demo:
>>> 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}}
Zauważ, że przewodnik po stylach Pythona PEP8 przepisuje nazwy snake_case dla funkcji. Powyższe działa równie dobrze w przypadku list lub mieszanki słowniki i listy, więc nazwy powinny być get_by_path()
i 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
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-07-29 21:12:32
- przyjęte rozwiązanie nie będzie działać bezpośrednio dla python3-będzie wymagało
from functools import reduce
. - wydaje się również bardziej pythoniczne użycie pętli
for
. Zobacz cytat z Co nowego w Pythonie 3.0.Usunięto
reduce()
. Użyjfunctools.reduce()
, jeśli naprawdę tego potrzebujesz; jednak w 99% przypadków wyraźna pętlafor
jest bardziej czytelna. - następnie zaakceptowane rozwiązanie nie ustawia nieistniejących zagnieżdżonych kluczy ( zwraca
KeyError
) - Zobacz odpowiedź @eafit rozwiązanie
Więc dlaczego nie użyć sugerowanej metody z pytania kolergy ' ego do uzyskania wartości:
def getFromDict(dataDict, mapList):
for k in mapList: dataDict = dataDict[k]
return dataDict
I kod z odpowiedzi @eafit do ustawiania wartości:
def nested_set(dic, keys, value):
for key in keys[:-1]:
dic = dic.setdefault(key, {})
dic[keys[-1]] = value
Oba działają prosto w Pythonie 2 i 3
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-03-18 15:43:29
Użycie reduce jest sprytne, ale metoda set op może mieć problemy, jeśli klucze nadrzędne nie istnieją wcześniej w zagnieżdżonym słowniku. Ponieważ jest to pierwszy tak post widziałem na ten temat w mojej wyszukiwarce google, chciałbym, aby to nieco lepiej.
Metoda set in ( Ustawianie wartości w zagnieżdżonym słowniku Pythona z listą indeksów i wartością) wydaje się bardziej odporna na brak kluczy rodzicielskich. Do skopiowania:
def nested_set(dic, keys, value):
for key in keys[:-1]:
dic = dic.setdefault(key, {})
dic[keys[-1]] = value
Również, może być wygodne, aby mieć metodę to przemierza drzewo kluczy i uzyskuje wszystkie bezwzględne ścieżki kluczowe, dla których utworzyłem:
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()], [])
Jednym z jego zastosowań jest konwersja zagnieżdżonego drzewa na ramkę danych pandy, używając następującego kodu (zakładając, że wszystkie liście w zagnieżdżonym słowniku mają tę samą głębokość).
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)
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:26:23
Ta biblioteka może być pomocna: https://github.com/akesterson/dpath-python
Biblioteka Pythona umożliwiająca dostęp i wyszukiwanie słowników poprzez / slashed / paths ala xpath
Zasadniczo pozwala przeglądać słownik tak, jakby był system plików.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-07-07 16:55:43
Zamiast wykonywać hit za każdym razem, gdy chcesz wyszukać wartość, co powiesz na spłaszczenie słownika raz, a następnie po prostu poszukaj klucza jak 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}
W ten sposób możesz po prostu wyszukać przedmioty za pomocą flat_dict['b:v:y']
, co da ci 1
.
I zamiast przemierzać słownik przy każdym wyszukiwaniu, możesz być w stanie przyspieszyć to, spłaszczając słownik i zapisując wyjście, aby wyszukiwanie od zimnego startu oznaczało załadowanie spłaszczonego słownika i po prostu wykonując wyszukiwanie klucza / wartości bez przechodzenia.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-03-01 02:07:58
A może użyjesz funkcji rekurencyjnych?
Aby uzyskać wartość:
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]
I ustawić wartość:
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
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-12-08 23:30:50
Alternatywny sposób, jeśli nie chcesz zgłaszać błędów, jeśli jeden z kluczy jest nieobecny (aby twój główny kod mógł działać bez przerwy):
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
W tym przypadku, jeśli którykolwiek z klawiszy wejściowych nie jest obecny, nie jest zwracany, co może być użyte jako sprawdzenie w kodzie głównym do wykonania alternatywnego zadania.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-03-16 01:30:53
Może sprawdzić, a następnie ustawić element dict bez dwukrotnego przetwarzania wszystkich indeksów?
Rozwiązanie:
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
Przykładowy przepływ pracy:
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()
Test
>>> 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()
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-08-22 03:40:29
Czysty styl Pythona, bez żadnego importu:
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)
Wyjście
{'foo': {'bar': 'yay'}}
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-02-16 14:00:54
Rozwiązałem to rekurencją:
def get(d,l):
if len(l)==1: return d[l[0]]
return get(d[l[0]],l[1:])
Używając Twojego przykładu:
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
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-03-05 13:38:11
Jeśli chcesz również pracować z dowolnymi json, w tym zagnieżdżonymi listami i dictami, i ładnie obsługiwać nieprawidłowe ścieżki wyszukiwania, oto moje rozwiązanie:
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
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-03-06 21:30:01