모든 중첩 된 사전 값을 반복 하시겠습니까?


120
for k, v in d.iteritems():
    if type(v) is dict:
        for t, c in v.iteritems():
            print "{0} : {1}".format(t, c)

사전을 반복하고 값이 중첩 된 사전이 아닌 모든 키 값 쌍을 인쇄하려고합니다. 값이 사전이면 그 안에 들어가서 키 값 쌍을 인쇄하고 싶습니다. 도움이 필요하세요?

편집하다

이건 어때요? 여전히 한 가지만 인쇄합니다.

def printDict(d):
    for k, v in d.iteritems():
        if type(v) is dict:
            printDict(v)
        else:
            print "{0} : {1}".format(k, v)

전체 테스트 케이스

사전:

{u'xml': {u'config': {u'portstatus': {u'status': u'good'}, u'target': u'1'},
      u'port': u'11'}}

결과:

xml : {u'config': {u'portstatus': {u'status': u'good'}, u'target': u'1'}, u'port': u'11'}

1
재귀를 원하는 것처럼 들리지만 설명이 확실하지 않습니다. 입 / 출력 예제는 어떻습니까? 또한 코드에 어떤 문제가 있습니까?
Niklas B.

2
Python에는 고정 된 재귀 제한이 있습니다. docs.python.org/library/sys.html#sys.setrecursionlimit
Dr. Jan-Philip Gehrcke

2
@ Jan-PhilipGehrcke : 재귀없이 트리와 같은 데이터 구조에 알고리즘을 구현하는 것은 자살입니다.
Niklas B.

2
@Takkun : dict변수 이름으로 사용 하고 있습니다. 절대 이렇게하지 마십시오 (이것이 실패한 이유입니다).
Niklas B.

3
@NiklasB., re : "suicide": 방금 Scharron 알고리즘의 반복 버전을 구현했으며 두 줄만 더 길고 따라 가기 쉽습니다. 게다가 재귀를 반복으로 변환하는 것은 종종 트리에서 일반 그래프로 이동할 때 요구 사항입니다.
Fred Foo

답변:


157

Niklas가 말했듯이 재귀가 필요합니다. 즉, dict를 인쇄 할 함수를 정의하고 값이 dict이면이 새로운 dict를 사용하여 인쇄 함수를 호출하고 싶습니다.

다음과 같은 것 :

def myprint(d):
    for k, v in d.items():
        if isinstance(v, dict):
            myprint(v)
        else:
            print("{0} : {1}".format(k, v))

3
작은 개선. myprint (v)를 호출하기 전에 print (k)를 추가하십시오.
Naomi Fridman

재귀를 사용하면 사소합니다.
sergzach

36

있다 잠재적 인 문제 당신이 당신의 자신의 재귀 구현 또는 스택 반복 동등한를 작성하는 경우는. 이 예를 참조하십시오.

    dic = {}
    dic["key1"] = {}
    dic["key1"]["key1.1"] = "value1"
    dic["key2"]  = {}
    dic["key2"]["key2.1"] = "value2"
    dic["key2"]["key2.2"] = dic["key1"]
    dic["key2"]["key2.3"] = dic

정상적인 의미에서 중첩 사전은 데이터 구조와 같은 n-nary 트리가됩니다. 그러나 정의 교차 가장자리 또는 심지어 뒤쪽 가장자리의 가능성을 배제하지 않습니다 (따라서 더 이상 나무가 아닙니다). 예를 들어, 여기 key2.2 에서 사전에 보유하고 키 1 , key2.3의 전체 사전에 점 (후면 가장자리 / 사이클). 백 엣지 (사이클)가 있으면 스택 / 재귀가 무한히 실행됩니다.

                          root<-------back edge
                        /      \           |
                     _key1   __key2__      |
                    /       /   \    \     |
               |->key1.1 key2.1 key2.2 key2.3
               |   /       |      |
               | value1  value2   |
               |                  | 
              cross edge----------|

Scharron 에서이 구현으로이 사전을 인쇄하면

    def myprint(d):
      for k, v in d.items():
        if isinstance(v, dict):
          myprint(v)
        else:
          print "{0} : {1}".format(k, v)

이 오류가 표시됩니다.

    RuntimeError: maximum recursion depth exceeded while calling a Python object

senderle 의 구현도 마찬가지 입니다.

마찬가지로 Fred Foo의 다음 구현으로 무한 루프를 얻습니다 .

    def myprint(d):
        stack = list(d.items())
        while stack:
            k, v = stack.pop()
            if isinstance(v, dict):
                stack.extend(v.items())
            else:
                print("%s: %s" % (k, v))

그러나 Python은 실제로 중첩 된 사전에서주기를 감지합니다.

    print dic
    {'key2': {'key2.1': 'value2', 'key2.3': {...}, 
       'key2.2': {'key1.1': 'value1'}}, 'key1': {'key1.1': 'value1'}}

"{...}" 는주기가 감지되는 곳입니다.

Moondra 가 요청 한대로 이것은주기 (DFS)를 피하는 방법입니다.

def myprint(d): 
  stack = list(d.items()) 
  visited = set() 
  while stack: 
    k, v = stack.pop() 
    if isinstance(v, dict): 
      if k not in visited: 
        stack.extend(v.items()) 
      else: 
        print("%s: %s" % (k, v)) 
      visited.add(k)

그렇다면 반복적 인 솔루션을 어떻게 구현 하시겠습니까?
dreftymac

2
@dreftymac 나는 사이클을 피하기 위해 키에 대한 방문 세트를 추가 할 것입니다 :def myprint(d): stack = d.items() visited = set() while stack: k, v = stack.pop() if isinstance(v, dict): if k not in visited: stack.extend(v.iteritems()) else: print("%s: %s" % (k, v)) visited.add(k)
tengr

1
지적 해 주셔서 감사합니다. 답변에 코드를 포함 해 주시겠습니까? 나는 그것이 당신의 훌륭한 대답을 완성한다고 생각합니다.
Moondra

Python3의 경우 as를 사용 하여 목록이 아닌보기 list(d.items())d.items()반환하고 v.items()대신v.iteritems()
Max

33

a dict는 반복 가능하므로 몇 가지 사소한 변경만으로 클래식 중첩 컨테이너 반복 가능 공식 을이 문제에 적용 할 수 있습니다 . 다음은 Python 2 버전입니다 (3은 아래 참조).

import collections
def nested_dict_iter(nested):
    for key, value in nested.iteritems():
        if isinstance(value, collections.Mapping):
            for inner_key, inner_value in nested_dict_iter(value):
                yield inner_key, inner_value
        else:
            yield key, value

테스트:

list(nested_dict_iter({'a':{'b':{'c':1, 'd':2}, 
                            'e':{'f':3, 'g':4}}, 
                       'h':{'i':5, 'j':6}}))
# output: [('g', 4), ('f', 3), ('c', 1), ('d', 2), ('i', 5), ('j', 6)]

Python 2에서는 Mapping 자격이 Mapping있지만 포함하지 않는 사용자 지정을 만들 있습니다 iteritems.이 경우 실패합니다. 문서는 그것이 iteritems필요한 것을 나타내지 않습니다 Mapping; 반면 소스Mapping유형에 iteritems메소드를 제공 합니다 . 따라서 custom의 경우 경우에 대비 Mappings하여 collections.Mapping명시 적으로 상속하십시오 .

Python 3에는 몇 가지 개선해야 할 사항이 있습니다. Python 3.3부터 추상 기본 클래스는 collections.abc. collections이전 버전과의 호환성을 위해 그대로 유지 되지만 추상 기본 클래스를 하나의 네임 스페이스에 함께 사용하는 것이 더 좋습니다. 이 수입 그래서 abc에서 collections. Python 3.3은 또한yield from 이러한 종류의 상황을 위해 합니다. 이것은 빈 구문 설탕이 아닙니다. 그것은 더 빠른 코드코 루틴 과의 더 현명한 상호 작용으로 이어질 수 있습니다 .

from collections import abc
def nested_dict_iter(nested):
    for key, value in nested.items():
        if isinstance(value, abc.Mapping):
            yield from nested_dict_iter(value)
        else:
            yield key, value

3
isinstance(item, collections.Iterable)에 대한 보장은 없습니다 hasattr(item, "iteritems"). 확인하는 collections.Mapping것이 좋습니다.
Fred Foo

1
@larsmans, 물론 당신 말이 맞습니다. 나는 사용 Iterable하면이 솔루션이 더 일반화 될 것이라고 생각하고 있었고 , 분명히 iterable이 반드시 iteritems.
senderle

이 문제에 대해 작동하는 일반적인 솔루션이기 때문에이 답변에 +1하지만 값을 인쇄하는 데 국한되지 않습니다. @Takkun 당신은 확실히이 옵션을 고려해야합니다. 장기적으로는 값을 인쇄하는 것 이상을 원할 것입니다.
Alejandro Piad

1
@ Seanny123, 이것에 관심을 가져 주셔서 감사합니다. 파이썬 3은 사실 몇 가지 방식으로 그림을 바꿉니다. 저는 이것을 새로운 yield from구문 을 사용하는 버전으로 다시 작성할 것 입니다.
senderle 2011

25

대체 반복 솔루션 :

def myprint(d):
    stack = d.items()
    while stack:
        k, v = stack.pop()
        if isinstance(v, dict):
            stack.extend(v.iteritems())
        else:
            print("%s: %s" % (k, v))

2
그래, 그게 내가 상상했던 방식이야. 감사. 그래서 이것의 장점은 매우 깊은 중첩을 위해 스택이 오버플로되지 않는다는 것입니다. 아니면 다른 것이 있습니까?
Niklas B.

@NiklasB .: 네, 이것이 첫 번째 이점입니다. 또한이 버전은 스택 (a list)을 a deque또는 우선 순위 대기열 로 대체하여 다른 순회 순서에 쉽게 적용 할 수 있습니다 .
Fred Foo

네, 말이됩니다. 감사합니다. 해피 코딩 :)
Niklas B.

예,하지만이 솔루션은 저와 재귀 솔루션보다 더 많은 공간을 소비합니다.
schlamar

1
@ ms4py : 재미 로 벤치 마크를 만들었습니다 . 내 컴퓨터에서 재귀 버전은 가장 빠르고 larsmans는 세 가지 테스트 사전 모두에서 두 번째입니다. 제너레이터를 사용하는 버전은 예상대로 비교적 느립니다 (다른 제너레이터 컨텍스트로 많은 저글링을해야하기 때문에)
Niklas B.

9

거기에 도착하는 길을 따라 키를 추적하는 약간 다른 버전

def print_dict(v, prefix=''):
    if isinstance(v, dict):
        for k, v2 in v.items():
            p2 = "{}['{}']".format(prefix, k)
            print_dict(v2, p2)
    elif isinstance(v, list):
        for i, v2 in enumerate(v):
            p2 = "{}[{}]".format(prefix, i)
            print_dict(v2, p2)
    else:
        print('{} = {}'.format(prefix, repr(v)))

데이터에 인쇄됩니다.

data['xml']['config']['portstatus']['status'] = u'good'
data['xml']['config']['target'] = u'1'
data['xml']['port'] = u'11'

필요한 경우 문자열이 아닌 키의 튜플로 접두사를 추적하도록 수정하는 것도 쉽습니다.


목록에 출력을 추가하는 방법은 무엇입니까?
Shash

5

여기에 비단뱀적인 방법이 있습니다. 이 함수를 사용하면 모든 수준에서 키-값 쌍을 반복 할 수 있습니다. 그것은 모든 것을 메모리에 저장하는 것이 아니라 당신이 그것을 반복하면서 dict를 따라 걷는다.

def recursive_items(dictionary):
    for key, value in dictionary.items():
        if type(value) is dict:
            yield (key, value)
            yield from recursive_items(value)
        else:
            yield (key, value)

a = {'a': {1: {1: 2, 3: 4}, 2: {5: 6}}}

for key, value in recursive_items(a):
    print(key, value)

인쇄물

a {1: {1: 2, 3: 4}, 2: {5: 6}}
1 {1: 2, 3: 4}
1 2
3 4
2 {5: 6}
5 6

2

대안으로 반복 솔루션 :

def traverse_nested_dict(d):
    iters = [d.iteritems()]

    while iters:
        it = iters.pop()
        try:
            k, v = it.next()
        except StopIteration:
            continue

        iters.append(it)

        if isinstance(v, dict):
            iters.append(v.iteritems())
        else:
            yield k, v


d = {"a": 1, "b": 2, "c": {"d": 3, "e": {"f": 4}}}
for k, v in traverse_nested_dict(d):
    print k, v

방법 것입니다? Big O는 동일해야합니다 ( O(depth)재귀 솔루션을위한 것입니다. 올바르게 생각하고 있다면이 버전에도 동일하게 적용됩니다).
Niklas B.

"스택 복사"? 무슨 소리 야? 모든 함수 호출은 새로운 스택 프레임을 생성합니다. 솔루션 iters이 명시 적 스택으로 사용 되므로 Big-O 메모리 소비가 동일합니까? 아니면 뭔가 빠졌습니까?
Niklas B.

@NiklasB. 재귀는 항상 오버 헤드를 동반합니다. 자세한 내용은 Wikipedia의이 섹션을 참조하십시오. en.wikipedia.org/wiki/… 재귀 솔루션의 스택 프레임은 훨씬 더 큽니다.
schlamar

당신은 그 단락을 오해하고있을 것입니다. 당신의 진술을 뒷받침하는 아무 말도하지 않습니다.
Niklas B.

1
@NiklasB. 아니요, 여기서 스택 프레임은 반복 일 뿐이고 재귀 솔루션의 경우 스택 프레임에는 반복, 프로그램 카운터, 변수 환경 등이 있습니다.
schlamar

2

Scharron의 솔루션을 기반으로하는 목록 작업을위한 대체 솔루션

def myprint(d):
    my_list = d.iteritems() if isinstance(d, dict) else enumerate(d)

    for k, v in my_list:
        if isinstance(v, dict) or isinstance(v, list):
            myprint(v)
        else:
            print u"{0} : {1}".format(k, v)

2

다음 코드를 사용하여 값이 사전을 포함하는 목록이 될 수있는 위치를 고려하여 중첩 된 사전의 모든 값을 인쇄합니다. 이것은 JSON 파일을 딕셔너리로 ​​파싱하고 그 값이 None.

    d = {
            "user": 10,
            "time": "2017-03-15T14:02:49.301000",
            "metadata": [
                {"foo": "bar"},
                "some_string"
            ]
        }


    def print_nested(d):
        if isinstance(d, dict):
            for k, v in d.items():
                print_nested(v)
        elif hasattr(d, '__iter__') and not isinstance(d, str):
            for item in d:
                print_nested(item)
        elif isinstance(d, str):
            print(d)

        else:
            print(d)

    print_nested(d)

산출:

    10
    2017-03-15T14:02:49.301000
    bar
    some_string

여기에 비슷한 문제가 있습니다 . stackoverflow.com/questions/50642922/… . 사전 목록의 마지막 요소를 찾아 삭제 한 다음 한 단계 위로 이동하는 방법이 있습니까? 삭제하지 않으면, 나는 목록을 반대하고 삭제할 수 있도록 마지막 요소는 데이터의 깊이가 어디 있는지 목록을 만들고 싶어
Heenashree 김시연을

1

다음은 Python 2에 대한 Fred Foo의 답변의 수정 된 버전입니다. 원래 응답에서는 가장 깊은 수준의 중첩 만 출력됩니다. 키를 목록으로 출력하면 모든 수준의 키를 유지할 수 있지만 참조하려면 목록 목록을 참조해야합니다.

기능은 다음과 같습니다.

def NestIter(nested):
    for key, value in nested.iteritems():
        if isinstance(value, collections.Mapping):
            for inner_key, inner_value in NestIter(value):
                yield [key, inner_key], inner_value
        else:
            yield [key],value

키를 참조하려면 :

for keys, vals in mynested: 
    print(mynested[keys[0]][keys[1][0]][keys[1][1][0]])

3 단계 사전의 경우.

여러 키에 액세스하기 전에 레벨 수를 알아야하며 레벨 수는 일정해야합니다 (값을 반복 할 때 중첩 레벨 수를 확인하기 위해 약간의 스크립트를 추가 할 수 있지만 아직 확인하지 못했습니다). 아직 봤다).


1

이 접근 방식이 좀 더 유연하다는 것을 알았습니다. 여기서 키, 값 쌍을 내보내고 목록을 반복하도록 쉽게 확장 할 수있는 생성기 함수 만 제공합니다.

def traverse(value, key=None):
    if isinstance(value, dict):
        for k, v in value.items():
            yield from traverse(v, k)
    else:
        yield key, value

그런 다음 사용자 고유의 myprint함수를 작성한 다음 해당 키 값 쌍을 인쇄 할 수 있습니다 .

def myprint(d):
    for k, v in traverse(d):
        print(f"{k} : {v}")

시험:

myprint({
    'xml': {
        'config': {
            'portstatus': {
                'status': 'good',
            },
            'target': '1',
        },
        'port': '11',
    },
})

산출:

status : good
target : 1
port : 11

나는 이것을 Python 3.6에서 테스트했습니다.


0

이 답변은 2 단계의 하위 사전에만 적용됩니다. 더 많은 것을 시도해보십시오 :

nested_dict = {'dictA': {'key_1': 'value_1', 'key_1A': 'value_1A','key_1Asub1': {'Asub1': 'Asub1_val', 'sub_subA1': {'sub_subA1_key':'sub_subA1_val'}}},
                'dictB': {'key_2': 'value_2'},
                1: {'key_3': 'value_3', 'key_3A': 'value_3A'}}

def print_dict(dictionary):
    dictionary_array = [dictionary]
    for sub_dictionary in dictionary_array:
        if type(sub_dictionary) is dict:
            for key, value in sub_dictionary.items():
                print("key=", key)
                print("value", value)
                if type(value) is dict:
                    dictionary_array.append(value)



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