web-dev-qa-db-ja.com

キーのリストを介してネストされた辞書項目にアクセスしますか?

正しい項目を指定するためにキーのリストを介してアクセスしたい複雑な辞書構造があります。

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
123
kolergy

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
185
Martijn Pieters
  1. 受け入れられた解決策は、python3では直接機能しません。from functools import reduceが必要です。
  2. また、forループを使用するほうがPythonicらしい。 What's New In Python 3. 。からの引用を参照してください。

    reduce()を削除しました。本当に必要な場合はfunctools.reduce()を使用してください。ただし、99%の時間で明示的なforループが読みやすくなります。

  3. 次に、受け入れられたソリューションは、存在しないネストされたキーを設定しません(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で直接動作します

31
DomTomCat

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

このライブラリは役に立つかもしれません: https://github.com/akesterson/dpath-python

/ slashed/paths ala xpath経由で辞書にアクセスして検索するためのpythonライブラリー

基本的に、それはまるでファイルシステムであるかのように辞書をグローブすることができます。

9
DMfll

再帰関数を使用してはどうですか?

値を取得するには:

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
3
xyres

純粋な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'}}
2
Arount

パーティーに非常に遅れましたが、将来これが誰かを助けるかもしれない場合に投稿します。私のユースケースでは、次の関数が最適に機能しました。辞書から任意のデータ型を引き出すために動作します

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
1
Jack Casey

すべてのインデックスを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()
1
And0k

値を検索するたびにパフォーマンスヒットを取得する代わりに、辞書を一度フラット化してから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
OkezieE

キーの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が返されます。これは、メインコードで代替タスクを実行するためのチェックとして使用できます。

1
Pulkit

文字列を連結する方法:

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
0
lucas

ネストされたリストや辞書を含む任意の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
0
Grant Palmer

@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
0
alancalvitti

再帰でこれを解決しました:

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
0
Poh Zi How

ネストされた属性を設定および取得するための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]
0
nehemiah