¿Recorrer todos los valores del diccionario anidados?

Resuelto Takkun asked hace 12 años • 18 respuestas

Estoy intentando recorrer un diccionario e imprimir todos los pares clave-valor donde el valor no es un diccionario anidado. Si el valor es un diccionario, quiero acceder a él e imprimir sus pares clave-valor, etc.

Intenté esto. Pero sólo funciona para los dos primeros niveles. Necesito que funcione para cualquier número de niveles.

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

También probé esto. Todavía solo imprime 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 de prueba completo:

Diccionario:

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

Resultado:

xml : {u'config': {u'portstatus': {u'status': u'good'}, u'target': u'1'}, u'port': u'11'}
Takkun avatar May 25 '12 21:05 Takkun
Aceptado

Como dijo Niklas, necesita recursividad, es decir, desea definir una función para imprimir su dictado, y si el valor es un dictado, desea llamar a su función de impresión usando este nuevo dictado.

Algo como :

def myprint(d):
    for k, v in d.items():
        if isinstance(v, dict):
            myprint(v)
        else:
            print("{0} : {1}".format(k, v))
Scharron avatar May 25 '2012 14:05 Scharron

Existen problemas potenciales si escribe su propia implementación recursiva o el equivalente iterativo con pila. Vea este ejemplo:

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

En el sentido normal, el diccionario anidado será una estructura de datos similar a un árbol n-nario. Pero la definición no excluye la posibilidad de un borde transversal o incluso un borde posterior (por lo tanto, ya no es un árbol). Por ejemplo, aquí key2.2 se refiere al diccionario desde key1 , key2.3 apunta a todo el diccionario (borde posterior/ciclo). Cuando hay un borde posterior (ciclo), la pila/recursión se ejecutará infinitamente.

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

Si imprime este diccionario con esta implementación de Scharron

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

Verías este error:

> RuntimeError: maximum recursion depth exceeded while calling a Python object

Lo mismo ocurre con la implementación de senderle .

De manera similar, obtienes un bucle infinito con esta implementación de 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))

Sin embargo, Python en realidad detecta ciclos en un diccionario anidado:

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

"{...}" es donde se detecta un ciclo.

Según lo solicitado por Moondra esta es una forma de evitar ciclos (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)
tengr avatar May 04 '2016 11:05 tengr

Dado que a dictes iterable, puede aplicar la clásica fórmula iterable de contenedor anidado a este problema con solo un par de cambios menores. Aquí hay una versión de Python 2 (ver 3 a continuación):

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

Prueba:

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

En Python 2, es posible crear una costumbre Mappingque califique como Mappingpero no contenga iteritems, en cuyo caso esto fallará. Los documentos no indican que iteritemssea necesario para un Mapping; por otro lado, la fuente proporciona Mappingun iteritemsmétodo a los tipos. Entonces, para custom Mappings, herede collections.Mappingexplícitamente por si acaso.

En Python 3, hay una serie de mejoras por realizar. A partir de Python 3.3, las clases base abstractas se encuentran en formato collections.abc. También permanecen collectionspor compatibilidad con versiones anteriores, pero es mejor tener nuestras clases base abstractas juntas en un espacio de nombres. Entonces esto se importa abcdesde collections. Python 3.3 también agrega yield from, que está diseñado precisamente para este tipo de situaciones. Esto no es azúcar sintáctico vacío; puede conducir a un código más rápido e interacciones más sensatas con las corrutinas .

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
senderle avatar May 25 '2012 14:05 senderle