Eseguire il ciclo di tutti i valori del dizionario annidati?


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

Sto cercando di scorrere un dizionario e stampare tutte le coppie di valori chiave in cui il valore non è un dizionario annidato. Se il valore è un dizionario, voglio entrare e stampare le sue coppie di valori chiave ... ecc. Qualsiasi aiuto?

MODIFICARE

Cosa ne pensi di questo? Stampa ancora solo una cosa.

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

Caso di prova completo

Dizionario:

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

Risultato:

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

1
Sembra che tu voglia la ricorsione, ma la descrizione non è abbastanza chiara per esserne sicuro. E qualche esempio di input / output? Inoltre, cosa c'è di sbagliato nel tuo codice?
Niklas B.

2
Esiste un limite di ricorsione fisso in Python: docs.python.org/library/sys.html#sys.setrecursionlimit
Dr. Jan-Philip Gehrcke

2
@ Jan-PhilipGehrcke: implementare algoritmi su una struttura dati ad albero senza ricorsione è un semplice suicidio.
Niklas B.

2
@ Takkun: stai usando dictcome nome di variabile. Non farlo mai (questo è il motivo per cui fallisce).
Niklas B.

3
@NiklasB., Re: "suicide": ho appena implementato una versione iterativa dell'algoritmo di Scharron e le sue sole due righe più lunghe e ancora abbastanza facili da seguire. Inoltre, tradurre la ricorsione in iterazione è spesso un requisito quando si passa dagli alberi ai grafici generali.
Fred Foo

Risposte:


157

Come detto da Niklas, hai bisogno della ricorsione, cioè vuoi definire una funzione per stampare il tuo dict, e se il valore è un dict, vuoi chiamare la tua funzione di stampa usando questo nuovo dict.

Qualcosa di simile a :

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

3
piccolo miglioramento. aggiungi print (k), prima di chiamare myprint (v).
Naomi Fridman

Con la ricorsione è banale.
sergzach

36

Ci sono potenziali problemi se scrivi la tua implementazione ricorsiva o l'equivalente iterativo con stack. Guarda questo esempio:

    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

Nel senso normale, il dizionario annidato sarà una struttura dati simile ad un albero n-nario. Ma la definizione non esclude la possibilità di un bordo trasversale o addirittura un bordo posteriore (quindi non più un albero). Per esempio, qui key2.2 tiene al dizionario da key1 , key2.3 punti per l'intero dizionario (bordo posteriore / ciclo). Quando c'è un bordo posteriore (ciclo), lo stack / ricorsione verrà eseguito all'infinito.

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

Se stampi questo dizionario con questa implementazione da Scharron

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

Vedresti questo errore:

    RuntimeError: maximum recursion depth exceeded while calling a Python object

Lo stesso vale con l'implementazione da senderle .

Allo stesso modo, ottieni un ciclo infinito con questa implementazione di 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))

Tuttavia, Python rileva effettivamente i cicli nel dizionario annidato:

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

"{...}" è dove viene rilevato un ciclo.

Come richiesto da Moondra questo è un modo per evitare i cicli (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)

quindi come implementeresti una soluzione iterativa?
dreftymac

2
@dreftymac Vorrei aggiungere un set di chiavi visitato per evitare di andare in bicicletta: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
Grazie per averlo fatto notare. Ti dispiacerebbe includere il tuo codice nella risposta. Penso che completi la tua eccellente risposta.
Moondra

Per Python3, usa list(d.items())come d.items()restituisce una vista, non un elenco, e usa v.items()invece div.iteritems()
Max

33

Poiché a dictè iterabile, è possibile applicare la classica formula iterabile del contenitore annidato a questo problema con solo un paio di piccole modifiche. Ecco una versione di Python 2 (vedi sotto per 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)]

In Python 2, potrebbe essere possibile creare un custom Mappingche si qualifica come a Mappingma non lo contiene iteritems, nel qual caso fallirà. I documenti non indicano che iteritemsè richiesto per un Mapping; d'altra parte, la fonte fornisce ai Mappingtipi un iteritemsmetodo. Quindi, per la personalizzazione Mappings, eredita da collections.Mappingesplicitamente per ogni evenienza.

In Python 3, ci sono una serie di miglioramenti da apportare. A partire da Python 3.3, le classi base astratte vivono in collections.abc. Rimangono collectionsanche per compatibilità con le versioni precedenti, ma è più bello avere le nostre classi base astratte insieme in un unico spazio dei nomi. Quindi questo importa abcda collections. Python 3.3 aggiunge anche yield from, che è progettato proprio per questo tipo di situazioni. Questo non è zucchero sintattico vuoto; può portare a codice più veloce e interazioni più sensate con le coroutine .

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)non è una garanzia per hasattr(item, "iteritems"). Controllare collections.Mappingè meglio.
Fred Foo

1
@larsmans, hai perfettamente ragione, ovviamente. Stavo pensando che l'utilizzo Iterableavrebbe reso questa soluzione più generalizzata, dimenticando che, ovviamente, gli iterabili non hanno necessariamente iteritems.
invio

+1 a questa risposta perché è una soluzione generale che funziona per questo problema, ma non si limita alla semplice stampa dei valori. @ Takkun dovresti assolutamente considerare questa opzione. A lungo termine vorrai qualcosa di più che stampare i valori.
Alejandro Piad

1
@ Seanny123, Grazie per aver attirato la mia attenzione su questo. Python 3 cambia l'immagine in un paio di modi, infatti - lo riscriverò come una versione che utilizza la nuova yield fromsintassi.
mittente

25

Soluzione iterativa alternativa:

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
Sì, è così che immaginavo che fosse. Grazie. Quindi il vantaggio di questo è che non trabocca lo stack per annidamenti estremamente profondi? O c'è qualcos'altro?
Niklas B.

@ NiklasB .: sì, questo è il primo vantaggio. Inoltre, questa versione può essere adattata abbastanza facilmente a diversi ordini di attraversamento sostituendo lo stack (a list) con una dequeo anche una coda prioritaria.
Fred Foo

Sì, ha senso. Grazie e buona codifica :)
Niklas B.

Sì, ma questa soluzione occupa più spazio della mia e di quella ricorsiva.
schlamar

1
@ ms4py: per divertimento, ho creato un benchmark . Sul mio computer, la versione ricorsiva è la più veloce e larsmans è la seconda per tutti e tre i dizionari di prova. La versione che utilizza i generatori è relativamente lenta, come previsto (perché ha a che fare molto con i diversi contesti del generatore)
Niklas B.

9

Versione leggermente diversa che ho scritto che tiene traccia delle chiavi lungo il percorso per arrivarci

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

Sui tuoi dati, verrà stampato

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

È anche facile modificarlo per tenere traccia del prefisso come una tupla di chiavi piuttosto che come una stringa se ne hai bisogno in questo modo.


Come aggiungere l'output a un elenco?
Shash

5

Ecco un modo pitonico per farlo. Questa funzione ti consentirà di scorrere la coppia chiave-valore in tutti i livelli. Non salva l'intera cosa nella memoria, ma piuttosto cammina attraverso il dict mentre lo ripeti

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)

stampe

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

3

Una soluzione alternativa per lavorare con elenchi basati sulla soluzione di 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

Soluzione iterativa come alternativa:

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

Com'è? Big O dovrebbe essere lo stesso (è O(depth)per la soluzione ricorsiva. Lo stesso vale per questa versione, se penso correttamente).
Niklas B.

"Copiare lo stack"? Di cosa stai parlando? Ogni chiamata di funzione crea un nuovo stackframe. La tua soluzione utilizza iterscome uno stack esplicito, quindi il consumo di memoria di Big-O è lo stesso o mi manca qualcosa?
Niklas B.

@NiklasB. La ricorsione arriva sempre con un sovraccarico, vedere questa sezione su Wikipedia per i dettagli: en.wikipedia.org/wiki/… Lo stack frame della soluzione ricorsiva è molto più grande.
schlamar

Devi aver frainteso quel paragrafo. Non dice nulla a sostegno delle tue affermazioni.
Niklas B.

1
@NiklasB. No, perché lo stack frame qui è solo l'iter e per la soluzione ricorsiva lo stack frame ha l'iter, il contatore del programma, la variabile d'ambiente, ecc ...
schlamar

2

Sto usando il seguente codice per stampare tutti i valori di un dizionario annidato, tenendo conto di dove il valore potrebbe essere un elenco contenente dizionari. Questo mi è stato utile durante l'analisi di un file JSON in un dizionario e per verificare rapidamente se uno dei suoi valori lo è 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)

Produzione:

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

Ho un problema molto simile qui stackoverflow.com/questions/50642922/… . C'è un modo per trovare l'ultimo elemento dell'elenco del dizionario, eliminarlo e poi salire di livello? Se non elimino, voglio creare un elenco in cui l'ultimo elemento è la profondità dei dati, quindi
inverto

1

Ecco una versione modificata della risposta di Fred Foo per Python 2. Nella risposta originale, viene emesso solo il livello più profondo di annidamento. Se si restituiscono le chiavi come elenchi, è possibile mantenere le chiavi per tutti i livelli, sebbene per fare riferimento ad esse sia necessario fare riferimento a un elenco di elenchi.

Ecco la funzione:

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

Per fare riferimento alle chiavi:

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

per un dizionario a tre livelli.

È necessario conoscere il numero di livelli prima di accedere a più chiavi e il numero di livelli dovrebbe essere costante (potrebbe essere possibile aggiungere un po 'di script per controllare il numero di livelli di nidificazione durante l'iterazione dei valori, ma non l'ho fatto eppure guardato questo).


1

Trovo questo approccio un po 'più flessibile, qui si fornisce solo la funzione di generatore che emette coppie di chiavi e valori e può essere facilmente esteso per iterare anche su elenchi.

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

Quindi puoi scrivere il tuo myprint funzione, quindi stampare quelle coppie di valori chiave.

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

Un test:

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

Produzione:

status : good
target : 1
port : 11

L'ho testato su Python 3.6.


0

Queste risposte funzionano solo per 2 livelli di sub-dizionari. Per di più prova questo:

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)
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.