正しい項目を指定するためにキーのリストを介してアクセスしたい複雑な辞書構造があります。
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
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
from functools import reduce
が必要です。for
ループを使用するほうがPythonicらしい。 What's New In Python 3. 。からの引用を参照してください。
reduce()
を削除しました。本当に必要な場合はfunctools.reduce()
を使用してください。ただし、99%の時間で明示的なfor
ループが読みやすくなります。
KeyError
を返します)-ソリューションについては@eafitの答えを参照してくださいしたがって、値を取得するためにkolergyの質問から提案された方法を使用しないのはなぜですか。
def getFromDict(dataDict, mapList):
for k in mapList: dataDict = dataDict[k]
return dataDict
そして、値を設定するための@eafitの答えからのコード:
def nested_set(dic, keys, value):
for key in keys[:-1]:
dic = dic.setdefault(key, {})
dic[keys[-1]] = value
両方ともpython 2および3で直接動作します
Reduceの使用は賢明ですが、ネストされたディクショナリに親キーが存在しない場合、OPのsetメソッドに問題がある場合があります。これは、Google検索でこのテーマについて見た最初のSO投稿なので、少し改善したいと思います。
( インデックスと値のリストを指定してネストされたpython辞書に値を設定する )のsetメソッドは、ペアレントキーが見つからない場合により堅牢なようです。コピーするには:
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()], [])
これの1つの使用法は、次のコードを使用して、ネストされたツリーをpandas DataFrameに変換することです(ネストされた辞書のすべてのリーフの深さが同じであると仮定)。
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)
このライブラリは役に立つかもしれません: https://github.com/akesterson/dpath-python
/ slashed/paths ala xpath経由で辞書にアクセスして検索するための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
純粋な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'}}
パーティーに非常に遅れましたが、将来これが誰かを助けるかもしれない場合に投稿します。私のユースケースでは、次の関数が最適に機能しました。辞書から任意のデータ型を引き出すために動作します
dictは、値を含む辞書です
リストは、私たちの価値に向けた「ステップ」のリストです
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
すべてのインデックスを2回処理せずに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()
値を検索するたびにパフォーマンスヒットを取得する代わりに、辞書を一度フラット化してから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
が得られます。
また、各ルックアップで辞書を走査する代わりに、辞書を平坦化して出力を保存することでこれを高速化でき、コールドスタートからのルックアップは平坦化された辞書をロードし、単純にキー/値ルックアップを実行することを意味しますトラバーサル。
キーの1つが存在しない場合にエラーを発生させたくない場合の代替方法(メインコードを中断せずに実行できるようにするため):
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が返されます。これは、メインコードで代替タスクを実行するためのチェックとして使用できます。
文字列を連結する方法:
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
ネストされたリストや辞書を含む任意のjsonで機能し、無効なルックアップパスを適切に処理する機能も必要な場合は、次の解決策があります。
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
@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
再帰でこれを解決しました:
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
ネストされた属性を設定および取得するための2つの静的メソッドを持つことについて、これらの答えを見ると満足です。これらのソリューションは、ネストされたツリーを使用するよりもはるかに優れています https://Gist.github.com/hrldcpr/201225
これが私の実装です。
使用法:
ネストされた属性を設定するには、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]