Zapętlić wszystkie zagnieżdżone wartości słownika?


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

Próbuję przejrzeć słownik i wydrukować wszystkie pary klucz-wartość, w przypadku których wartość nie jest zagnieżdżonym słownikiem. Jeśli wartość jest słownikiem, chcę do niego wejść i wydrukować pary klucz-wartość ... itd. Jakaś pomoc?

EDYTOWAĆ

Co powiesz na to? Nadal drukuje tylko jedną rzecz.

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

Pełny przypadek testowy

Słownik:

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

Wynik:

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

1
Wygląda na to, że chcesz rekursji, ale opis nie jest wystarczająco jasny, aby mieć pewność. A co z jakimś przykładem wejścia / wyjścia? Co jest nie tak z twoim kodem?
Niklas B.

2
W Pythonie istnieje ustalony limit rekursji: docs.python.org/library/sys.html#sys.setrecursionlimit
Dr Jan-Philip Gehrcke

2
@ Jan-PhilipGehrcke: Zaimplementowanie algorytmów na drzewiastej strukturze danych bez rekursji to zwykłe samobójstwo.
Niklas B.

2
@Takkun: używasz dictjako nazwy zmiennej. Nigdy tego nie rób (dlatego się nie udaje).
Niklas B.

3
@NiklasB., Re: "suicide": Właśnie zaimplementowałem iteracyjną wersję algorytmu Scharrona, która jest tylko o dwie linie dłuższa i nadal jest łatwa do naśladowania. Poza tym, tłumaczenie rekurencji na iterację jest często wymagane przy przechodzeniu od drzew do ogólnych wykresów.
Fred Foo

Odpowiedzi:


158

Jak powiedział Niklas, potrzebujesz rekurencji, tj. Chcesz zdefiniować funkcję, która wydrukuje twój dykt, a jeśli wartością jest dict, chcesz wywołać swoją funkcję print używając tego nowego dyktu.

Coś jak :

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

3
mała poprawa. dodaj print (k), przed wywołaniem myprint (v).
Naomi Fridman

W przypadku rekurencji jest to trywialne.
sergzach

37

Istnieją potencjalne problemy, jeśli napiszesz własną implementację rekurencyjną lub iteracyjny odpowiednik ze stosem. Zobacz ten przykład:

    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

W normalnym sensie słownik zagnieżdżony będzie n-arowym drzewem, podobnym do struktury danych. Ale definicja nie wyklucza możliwości krawędzi poprzecznej lub nawet tylnej krawędzi (a zatem nie jest już drzewem). Na przykład, o key2.2 posiada słownika z key1 , key2.3 wskazuje na całej tylnej krawędzi (słowniku / stopień). Kiedy występuje tylna krawędź (cykl), stos / rekursja będzie działać w nieskończoność.

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

Jeśli drukujesz ten słownik z tą implementacją firmy Scharron

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

Zobaczysz ten błąd:

    RuntimeError: maximum recursion depth exceeded while calling a Python object

To samo dotyczy implementacji od senderle .

Podobnie, dzięki tej implementacji Freda Foo otrzymujesz nieskończoną pętlę :

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

Jednak Python faktycznie wykrywa cykle w zagnieżdżonym słowniku:

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

„{...}” to miejsce, w którym wykrywany jest cykl.

Zgodnie z życzeniem firmy Moondra jest to sposób na uniknięcie cykli (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)

więc jak zaimplementowałbyś rozwiązanie iteracyjne?
dreftymac

2
@dreftymac Dodałbym odwiedzany zestaw kluczy, aby uniknąć przechodzenia cykli: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
Dziękuję za zwrócenie uwagi. Czy mógłbyś włączyć swój kod do odpowiedzi. Myślę, że to uzupełnia twoją doskonałą odpowiedź.
Moondra

W przypadku Python3 użyj list(d.items())as d.items()zwraca widok, a nie listę, i użyj v.items()zamiastv.iteritems()
Max

33

Ponieważ a dictjest iterowalny, możesz zastosować do tego problemu klasyczną iterowalną formułę zagnieżdżonego kontenera, dokonując tylko kilku drobnych zmian. Oto wersja Pythona 2 (patrz poniżej 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

Test:

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)]

W Pythonie 2 może być możliwe utworzenie niestandardowego, Mappingktóry kwalifikuje się jako a, Mappingale nie zawiera iteritems, w takim przypadku zakończy się to niepowodzeniem. Dokumenty nie wskazują, że iteritemsjest to wymagane dla Mapping; z drugiej strony źródło podaje Mappingtypom iteritemsmetodę. Tak więc w przypadku niestandardowych Mappingsdziedzicz z collections.Mappingwyraźnie na wszelki wypadek.

W Pythonie 3 jest wiele ulepszeń, które należy wprowadzić. Począwszy od Pythona 3.3, istnieją abstrakcyjne klasy bazowe collections.abc. Pozostają one collectionsrównież w celu zachowania kompatybilności wstecznej, ale przyjemniej jest mieć nasze abstrakcyjne klasy bazowe razem w jednej przestrzeni nazw. Więc to importuje abcz collections. Python 3.3 również dodaje yield from, który jest przeznaczony tylko do tego rodzaju sytuacji. To nie jest pusty cukier syntaktyczny; może to prowadzić do szybszego kodu i bardziej sensownych interakcji z programami .

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)nie ma gwarancji hasattr(item, "iteritems"). Sprawdzanie collections.Mappingjest lepsze.
Fred Foo,

1
@larsmans, masz oczywiście rację. Myślałem, że użycie Iterableuczyniłoby to rozwiązanie bardziej uogólnionym, zapominając, że oczywiście iterowalne niekoniecznie mają iteritems.
senderle

+1 do tej odpowiedzi, ponieważ jest to ogólne rozwiązanie, które działa w przypadku tego problemu, ale nie ogranicza się tylko do drukowania wartości. @Takkun zdecydowanie powinieneś rozważyć tę opcję. Na dłuższą metę będziesz chciał czegoś więcej niż tylko wydrukować wartości.
Alejandro Piad

1
@ Seanny123, Dziękuję za zwrócenie mi na to uwagi. W rzeczywistości Python 3 zmienia obraz na kilka sposobów - zamierzam przepisać to jako wersję, która używa nowej yield fromskładni.
senderle

25

Alternatywne rozwiązanie iteracyjne:

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
Tak, tak to sobie wyobrażałem. Dzięki. Więc zaletą tego jest to, że nie przepełni on stosu w przypadku bardzo głębokich zagnieżdżeń? A może jest coś jeszcze?
Niklas B.

@NiklasB .: tak, to pierwsza korzyść. Ponadto tę wersję można dość łatwo dostosować do różnych kolejności przechodzenia, zastępując stos (a list) dequekolejką lub nawet kolejką priorytetową.
Fred Foo,

Tak, ma sens. Dziękuję i życzę miłego kodowania :)
Niklas B.

Tak, ale to rozwiązanie zajmuje więcej miejsca niż moje i rekurencyjne.
schlamar

1
@ ms4py: Dla zabawy stworzyłem benchmark . Na moim komputerze wersja rekurencyjna jest najszybsza, a larsmans zajmuje drugie miejsce we wszystkich trzech słownikach testowych. Wersja wykorzystująca generatory jest stosunkowo powolna, zgodnie z oczekiwaniami (ponieważ musi dużo żonglować różnymi kontekstami generatorów)
Niklas B.

10

Nieco inna wersja, którą napisałem, która śledzi klucze po drodze, aby się tam dostać

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

Na twoich danych zostanie wydrukowany

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

Łatwo jest go również zmodyfikować, aby śledzić prefiks jako krotkę kluczy, a nie ciąg znaków, jeśli potrzebujesz tego w ten sposób.


Jak dodać wynik do listy?
Shash

5

Oto pythonowy sposób na zrobienie tego. Ta funkcja umożliwia przechodzenie przez parę klucz-wartość na wszystkich poziomach. Nie zapisuje całości w pamięci, ale raczej przechodzi przez dyktowanie, gdy przechodzisz przez pętlę

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)

Wydruki

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

3

Alternatywne rozwiązanie do pracy z listami opartymi na rozwiązaniu 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

Rozwiązanie iteracyjne jako alternatywa:

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

W jaki sposób? Duże O powinno być takie samo (dotyczy O(depth)rozwiązania rekurencyjnego. To samo dotyczy tej wersji, jeśli dobrze myślę).
Niklas B.

„Skopiuj stos”? O czym mówisz? Każde wywołanie funkcji tworzy nową ramkę stosu. Twoje rozwiązanie używa itersjako jawnego stosu, więc zużycie pamięci Big-O jest takie samo, czy czegoś mi brakuje?
Niklas B.

@NiklasB. Rekursja zawsze wiąże się z narzutem, szczegóły w tej sekcji Wikipedii: en.wikipedia.org/wiki/… Ramka stosu rozwiązania rekurencyjnego jest znacznie większa.
schlamar

Musisz źle zrozumieć ten akapit. Nie mówi nic na poparcie twoich stwierdzeń.
Niklas B.

1
@NiklasB. Nie, ponieważ ramka stosu jest tutaj tylko iterem, a dla rozwiązania rekurencyjnego ramka stosu ma iter, licznik programu, zmienne środowisko itp.
schlamar

2

Używam następującego kodu, aby wydrukować wszystkie wartości zagnieżdżonego słownika, biorąc pod uwagę, gdzie wartością może być lista zawierająca słowniki. Było to przydatne podczas analizowania pliku JSON w słowniku i konieczności szybkiego sprawdzenia, czy któraś z jego wartości jest 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)

Wynik:

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

Mam tutaj dużo podobny problem stackoverflow.com/questions/50642922/… . Czy jest sposób, aby znaleźć ostatni element listy słownika, usunąć go, a następnie przenieść poziom wyżej? Jeśli nie usuwam, chcę zrobić listę, gdzie ostatnim elementem jest głębokość danych, więc odwracam listę i
usuwam

1

Oto zmodyfikowana wersja odpowiedzi Freda Foo dla Pythona 2. W oryginalnej odpowiedzi wyprowadzany jest tylko najgłębszy poziom zagnieżdżenia. Jeśli wyprowadzasz klucze jako listy, możesz zachować klucze na wszystkich poziomach, chociaż aby się do nich odwołać, musisz odwołać się do listy list.

Oto funkcja:

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

Aby odwołać się do kluczy:

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

dla słownika trzypoziomowego.

Musisz znać liczbę poziomów zanim uzyskasz dostęp do wielu kluczy, a liczba poziomów powinna być stała (może być możliwe dodanie małego skryptu, aby sprawdzić liczbę poziomów zagnieżdżenia podczas iteracji przez wartości, ale nie jeszcze spojrzał na to).


1

Uważam, że to podejście jest nieco bardziej elastyczne, tutaj po prostu zapewniasz funkcję generatora, która emituje pary kluczy i wartości i może być łatwo rozszerzona, aby również iterować po listach.

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

Następnie możesz napisać własną myprintfunkcję, a następnie wydrukować te pary klucz-wartość.

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

Test:

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

Wynik:

status : good
target : 1
port : 11

Przetestowałem to na Pythonie 3.6.


0

Te odpowiedzi działają tylko dla 2 poziomów słowników podrzędnych. Aby uzyskać więcej, spróbuj tego:

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)
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.