키 목록을 통해 중첩 된 사전 항목에 액세스 하시겠습니까?


143

올바른 항목을 처리하기 위해 키 목록을 통해 액세스하려는 복잡한 사전 구조가 있습니다.

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

답변:


230

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

1
임의의 중첩 구조에 대해 이러한 이송이 얼마나 신뢰할 수 있습니까? 중첩 된 목록이있는 혼합 사전에서도 작동합니까? getFromDict ()를 수정하여 default_value를 제공하고 default default_value를 None으로 설정하려면 어떻게합니까? 나는 수년간의 PHP 개발과 C 개발 이전의 Python에서 초보자입니다.
Dmitriy Sintsov

2
또한 중첩 된 맵핑 세트는 존재하지 않는 노드, imo : 정수 키 목록, 문자열 키 사전을 작성해야합니다.
Dmitriy Sintsov

1
@ user1353510 : 발생하는대로 정기적 인 색인 구문이 사용되므로 사전 내부 목록도 지원합니다. 정수 인덱스를 전달하십시오.
Martijn Pieters

1
@ user1353510 : 디폴트 값을 사용하기 위해 try:, except (KeyError, IndexError): return default_value현재 주위 return라인.
Martijn Pieters

1
@Georgy :를 사용 dict.get()하면 시맨틱이 변경되어 누락 된 이름이 나오지 None않고 반환 KeyError됩니다. 이후의 모든 이름은을 트리거합니다 AttributeError. operator표준 라이브러리이므로 여기서 피할 필요가 없습니다.
Martijn Pieters

40
  1. 허용 된 솔루션은 python3에서 직접 작동하지 않습니다 from functools import reduce.
  2. 또한 for루프 를 사용하는 것이 더 pythonic처럼 보입니다 . Python 3.0의 새로운 기능 에서 인용 한 내용을 참조하십시오 .

    제거되었습니다 reduce(). functools.reduce()정말로 필요한 경우 사용하십시오 . 그러나 명시 적 for루프가 더 읽기 쉬운 시간의 99 %입니다 .

  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

둘 다 파이썬 2와 3에서 바로 작동합니다.


6
나는이 솔루션을 선호하지만 조심하십시오. 내가 실수하지 않으면 파이썬 사전은 불변이기 때문에 getFromDict호출자의를 파괴 할 가능성이 dataDict있습니다. 나는 copy.deepcopy(dataDict)먼저 것입니다. 물론, (기록 된대로)이 동작은 두 번째 기능에서 바람직합니다.
Dylan F

15

reduce 사용은 영리하지만 부모 키가 중첩 된 사전에 존재하지 않으면 OP의 set 메소드에 문제가있을 수 있습니다. 이것은 내 Google 검색 에서이 주제에 대해 본 첫 번째 게시물이므로 조금 더 좋게 만들고 싶습니다.

( 인덱스 및 값 목록이 제공된 중첩 된 파이썬 사전에 값 설정) 의 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()], [])

이를 사용하는 방법은 다음 코드를 사용하여 중첩 트리를 팬더 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)

왜 'keys'인수 길이를 2 이상으로 임의로 제한 nested_set합니까?
alancalvitti

10

이 라이브러리가 도움이 될 수 있습니다 : https://github.com/akesterson/dpath-python

/ slashed / paths를 통해 사전에 액세스하고 검색하기위한 Python 라이브러리

기본적으로 파일 시스템 인 것처럼 사전을 넘길 수 있습니다.


3

재귀 함수를 사용하는 것은 어떻습니까?

가치를 얻으려면 :

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

2

가져 오기없이 순수한 파이썬 스타일 :

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

키 중 하나가없는 경우 오류를 발생시키지 않으려는 다른 방법 (메인 코드가 중단없이 실행될 수 있음) :

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

값을 찾을 때마다 성능을 높이는 대신 사전을 한 번 평평하게 한 다음 키를 간단히 찾는 방법은 어떻습니까? 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(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

1

모든 인덱스를 두 번 처리하지 않고 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

파티에 매우 늦었지만 나중에이를 위해 도움이 될 수있는 경우에 게시합니다. 내 유스 케이스의 경우 다음 기능이 가장 효과적이었습니다. 사전에서 모든 데이터 유형을 가져옵니다.

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

1

중첩 된 속성을 설정하고 얻는 두 가지 정적 방법을 사용하면이 답변을 보는 것이 만족 스럽습니다. 이 솔루션은 중첩 트리를 사용하는 것보다 낫습니다 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]

1

python-benedictkeypath 를 사용 하여 중첩 된 항목에 액세스 하는 것이 좋습니다 .

다음을 사용하여 설치하십시오 pip.

pip install python-benedict

그때:

from benedict import benedict

dataDict = benedict({
    "a":{
        "r": 1,
        "s": 2,
        "t": 3,
    },
    "b":{
        "u": 1,
        "v": {
            "x": 1,
            "y": 2,
            "z": 3,
        },
        "w": 3,
    },
}) 

print(dataDict['a.r'])
# or
print(dataDict['a', 'r'])

여기에 전체 문서 : https://github.com/fabiocaccamo/python-benedict


0

중첩 된 목록과 dicts를 포함하여 임의의 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

문자열을 연결하는 방법 :

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

@DomTomCat 및 기타 접근 방식을 확장하여 이러한 기능 (즉, 입력에 영향을주지 않고 심도 복사를 통해 수정 된 데이터를 반환) setter 및 mapper는 중첩 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

eval파이썬 에서 함수를 사용할 수 있습니다 .

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기능 사용으로 인해 발생할 수있는 취약점에주의해야합니다 . 토론은 여기에서 찾을 수 있습니다 :

  1. https://nedbatchelder.com/blog/201206/eval_really_is_dangerous.html
  2. https://www.journaldev.com/22504/python-eval-function

에서 nested_parse()기능, 나는 더 있는지 만들었 __builtins__전역이 가능하고 사용할 수있는 경우에만 지역 변수없는 nest사전.


당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.