¿Recorrer todos los valores de diccionario anidados?

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

Estoy tratando de recorrer un diccionario e imprimir todos los pares de valores clave donde el valor no es un diccionario anidado. Si el valor es un diccionario, quiero ir a él e imprimir sus pares de valores clave ... etc. ¿Alguna ayuda?

EDITAR

¿Qué tal esto? Todavía imprime una sola 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
fuente
1
Parece que desea recursividad, pero la descripción no es lo suficientemente clara como para estar seguro. ¿Qué pasa con algún ejemplo de entrada / salida? Además, ¿qué pasa con tu código?
Niklas B.
2
Hay un límite de recursividad fijo en Python: docs.python.org/library/sys.html#sys.setrecursionlimit
Dr. Jan-Philip Gehrcke
2
@ Jan-PhilipGehrcke: Implementar algoritmos en una estructura de datos en forma de árbol sin recursividad es un suicidio.
Niklas B.
2
@Takkun: Está utilizando dictcomo nombre de variable. Nunca hagas esto (es por eso que falla).
Niklas B.
3
@NiklasB., Re: "suicide": Acabo de implementar una versión iterativa del algoritmo de Scharron y solo tiene dos líneas más largas y aún es bastante fácil de seguir. Además, traducir la recursividad en iteración es a menudo un requisito cuando se pasa de árboles a gráficos generales.
Fred Foo

Respuestas:

157

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

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
fuente
3
pequeña mejora. agregue print (k), antes de llamar a myprint (v).
Naomi Fridman
Con la recursividad es trivial.
sergzach
36

Existen problemas potenciales si escribe su propia implementación recursiva o el equivalente iterativo con stack. 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 mantiene al diccionario de key1 , key2.3 puntos a todo el diccionario (borde posterior / ciclo). Cuando hay un borde posterior (ciclo), la pila / recursividad 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ía este error:

    RuntimeError: maximum recursion depth exceeded while calling a Python object

Lo mismo ocurre con la implementación de senderle .

Del mismo modo, 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 el 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
fuente
Entonces, ¿cómo implementaría una solución iterativa?
dreftymac
2
@dreftymac Agregaría un conjunto visitado de claves para evitar ciclos: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
Gracias por señalar esto. ¿Le importaría incluir su código en la respuesta? Creo que completa tu excelente respuesta.
Moondra
Para Python3, use list(d.items())as d.items()devuelve una vista, no una lista, y use en v.items()lugar dev.iteritems()
Max
33

Dado que a dictes iterable, puede aplicar la fórmula iterable de contenedor anidado clásica a este problema con solo un par de cambios menores. Aquí hay una versión de Python 2 (consulte la 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, podría ser posible crear una personalizada Mappingque califique como Mappingpero no contenga iteritems, en cuyo caso esto fallará. Los documentos no indican que iteritemssea ​​necesario para a Mapping; por otro lado, la fuente da a los Mappingtipos un iteritemsmétodo. Entonces, para la costumbre Mappings, herede collections.Mappingexplícitamente por si acaso.

En Python 3, se deben realizar una serie de mejoras. A partir de Python 3.3, las clases base abstractas viven en collections.abc. También se mantienen collectionspara compatibilidad con versiones anteriores, pero es mejor tener nuestras clases base abstractas juntas en un espacio de nombres. Entonces esto importa abcde collections. Python 3.3 también agrega yield from, que está diseñado solo 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 sensibles 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
remitente
fuente
3
isinstance(item, collections.Iterable)no hay garantía para hasattr(item, "iteritems"). Verificar collections.Mappinges mejor.
Fred Foo
1
@larsmans, tienes toda la razón, por supuesto. Estaba pensando que usar Iterableharía que esta solución fuera más generalizada, olvidando que, obviamente, los iterables no necesariamente lo tienen iteritems.
remitente
+1 a esta respuesta porque es una solución general que funciona para este problema, pero no se limita a imprimir los valores. @Takkun definitivamente deberías considerar esta opción. A la larga, querrá algo más que imprimir los valores.
Alejandro Piad
1
@ Seanny123, Gracias por llamar mi atención sobre esto. Python 3 cambia la imagen de un par de formas, de hecho, voy a reescribir esto como una versión que usa la nueva yield fromsintaxis.
senderle
25

Solución 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))
Fred Foo
fuente
2
Sí, así es como me lo imaginé. Gracias. Entonces, ¿la ventaja de esto es que no desbordará la pila para anidamientos extremadamente profundos? ¿O hay algo más en eso?
Niklas B.
@NiklasB .: sí, ese es el primer beneficio. Además, esta versión se puede adaptar a diferentes órdenes de recorrido con bastante facilidad reemplazando la pila (a list) por una dequeo incluso una cola de prioridad.
Fred Foo
Sí, tiene sentido. Gracias y feliz codificación :)
Niklas B.
Sí, pero esta solución consume más espacio que la mía y la recursiva.
schlamar
1
@ ms4py: Por diversión, creé un punto de referencia . En mi computadora, la versión recursiva es la más rápida y larsmans es la segunda para los tres diccionarios de prueba. La versión que usa generadores es relativamente lenta, como se esperaba (porque tiene que hacer muchos malabarismos con los diferentes contextos del generador)
Niklas B.
9

Escribí una versión ligeramente diferente que realiza un seguimiento de las teclas en el camino para llegar allí

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

En sus datos, se imprimirá

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

También es fácil modificarlo para rastrear el prefijo como una tupla de claves en lugar de una cadena si lo necesita de esa manera.

Ehsan Kia
fuente
¿Cómo agregar la salida a una lista?
Shash
5

Aquí hay una forma pitónica de hacerlo. Esta función le permitirá recorrer el par clave-valor en todos los niveles. No guarda todo en la memoria, sino que recorre el dict a medida que lo recorre.

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)

Huellas dactilares

a {1: {1: 2, 3: 4}, 2: {5: 6}}
1 {1: 2, 3: 4}
1 2
3 4
2 {5: 6}
5 6
Dmitry Torba
fuente
3

Una solución alternativa para trabajar con listas basadas en la solución de 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)
Gabriel
fuente
2

Solución iterativa como 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
schlamar
fuente
¿Como es eso? Big O debería ser el mismo (es O(depth)para la solución recursiva. Lo mismo se aplica a esta versión, si estoy pensando correctamente).
Niklas B.
¿"Copiar la pila"? ¿De qué estás hablando? Cada llamada a función crea un nuevo stackframe. Su solución se utiliza iterscomo una pila explícita, por lo que el consumo de memoria de Big-O es el mismo, ¿o me falta algo?
Niklas B.
@NiklasB. La recursividad siempre viene con sobrecarga, consulte esta sección en Wikipedia para obtener más detalles: en.wikipedia.org/wiki/… El marco de pila de la solución recursiva es mucho más grande.
schlamar
Debe haber entendido mal ese párrafo. No dice nada para respaldar sus declaraciones.
Niklas B.
1
@NiklasB. No, porque el marco de la pila aquí es solo el iter y para la solución recursiva el marco de la pila tiene el iter, el contador del programa, el entorno variable, etc.
schlamar
2

Estoy usando el siguiente código para imprimir todos los valores de un diccionario anidado, teniendo en cuenta dónde el valor podría ser una lista que contenga diccionarios. Esto fue útil para mí cuando analicé un archivo JSON en un diccionario y necesité verificar rápidamente si alguno de sus valores es 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)

Salida:

    10
    2017-03-15T14:02:49.301000
    bar
    some_string
sigma
fuente
Tengo un problema muy similar aquí stackoverflow.com/questions/50642922/… . ¿Hay alguna forma de encontrar el último elemento de la lista del diccionario, eliminarlo y luego subir un nivel? Si no se elimina, quiero hacer una lista donde el último elemento es la profundidad de los datos, así que
invierto
1

Aquí hay una versión modificada de la respuesta de Fred Foo para Python 2. En la respuesta original, solo se genera el nivel más profundo de anidación. Si genera las claves como listas, puede conservar las claves para todos los niveles, aunque para hacer referencia a ellas debe hacer referencia a una lista de listas.

Esta es la función:

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

Para hacer referencia a las claves:

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

para un diccionario de tres niveles.

Necesita saber el número de niveles antes de acceder a múltiples claves y el número de niveles debe ser constante (puede ser posible agregar un poco de secuencia de comandos para verificar el número de niveles de anidación al iterar a través de valores, pero no lo he hecho aún miré esto).

PicanteBaguette
fuente
1

Encuentro este enfoque un poco más flexible, aquí solo proporciona una función de generador que emite pares de claves y valores y se puede extender fácilmente para iterar también sobre listas.

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

Luego, puede escribir su propia myprintfunción y luego imprimir esos pares clave-valor.

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

Una prueba:

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

Salida:

status : good
target : 1
port : 11

Probé esto en Python 3.6.

sirex
fuente
0

Estas respuestas funcionan solo para 2 niveles de subdiccionarios. Para más prueba esto:

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