Fazer um loop por todos os valores do dicionário aninhados?


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

Estou tentando percorrer um dicionário e imprimir todos os pares de valores-chave onde o valor não é um dicionário aninhado. Se o valor for um dicionário, quero ir até ele e imprimir seus pares de valores-chave ... etc. Qualquer ajuda?

EDITAR

Que tal agora? Ele ainda imprime apenas uma coisa.

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 Teste Completo

Dicionário:

{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'}

1
Parece que você deseja recursão, mas a descrição não é clara o suficiente para ter certeza. Que tal alguns exemplos de entrada / saída? Além disso, o que há de errado com seu código?
Niklas B.


2
@ Jan-PhilipGehrcke: Implementar algoritmos em uma estrutura de dados semelhante a uma árvore sem recursão é um puro suicídio.
Niklas B.

2
@Takkun: Você está usando dictum nome de variável. Nunca faça isso (é por isso que falha).
Niklas B.

3
@NiklasB., Re: "suicídio": Acabei de implementar uma versão iterativa do algoritmo de Scharron com apenas duas linhas a mais e ainda muito fácil de seguir. Além disso, traduzir a recursão em iteração é frequentemente um requisito ao passar de árvores para gráficos gerais.
Fred Foo

Respostas:


156

Como disse Niklas, você precisa de recursão, ou seja, você deseja definir uma função para imprimir seu dicionário, e se o valor for um dicionário, você deseja chamar sua função de impressão usando este novo dicionário.

Algo como :

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

3
pequena melhoria. adicione print (k), antes de chamar myprint (v).
Naomi Fridman

Com recursão, é trivial.
sergzach

36

Existem problemas potenciais se você escrever sua própria implementação recursiva ou o equivalente iterativo com pilha. Veja este exemplo:

    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

No sentido normal, o dicionário aninhado será uma árvore n-nária como a estrutura de dados. Mas a definição não exclui a possibilidade de uma borda cruzada ou mesmo uma borda posterior (portanto, não é mais uma árvore). Por exemplo, aqui key2.2 abrange o dicionário de key1 , key2.3 aponta para todo o dicionário (borda posterior / ciclo). Quando houver uma borda posterior (ciclo), a pilha / recursão será executada infinitamente.

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

Se você imprimir este dicionário com esta implementação de Scharron

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

Você veria este erro:

    RuntimeError: maximum recursion depth exceeded while calling a Python object

O mesmo acontece com a implementação do remetente .

Da mesma forma, você obtém um loop infinito com esta implementação 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))

No entanto, o Python realmente detecta ciclos no dicionário aninhado:

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

"{...}" é onde um ciclo é detectado.

Conforme solicitado pela Moondra, esta é uma 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)

então, como você implementaria uma solução iterativa?
dreftymac

2
@dreftymac Eu adicionaria um conjunto visitado de chaves 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
Obrigado por apontar isso. Você se importaria de incluir seu código na resposta. Acho que completa sua excelente resposta.
Moondra

Para Python3, use list(d.items())as d.items()retorna uma visualização, não uma lista, e use em v.items()vez dev.iteritems()
Máx.

33

Como a dicté iterável, você pode aplicar a fórmula iterável de contêiner aninhado clássica a esse problema com apenas algumas pequenas alterações. Esta é uma versão do Python 2 (veja a 3 abaixo):

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

Teste:

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

No Python 2, pode ser possível criar um personalizado Mappingque se qualifique como um, Mappingmas não contenha iteritems; nesse caso, haverá falha. Os documentos não indicam que iteritemsé necessário para um Mapping; por outro lado, a fonte fornece aos Mappingtipos um iteritemsmétodo. Então, para customizar Mappings, herde decollections.Mapping explicitamente apenas no caso.

No Python 3, há uma série de melhorias a serem feitas. A partir do Python 3.3, classes básicas abstratas estão presentes collections.abc. Eles permanecem dentro collectionstambém para compatibilidade com versões anteriores, mas é melhor ter nossas classes básicas abstratas juntas em um namespace. Então, isso importa abcde collections. Python 3.3 também adiciona yield from, que é projetado exatamente para esse tipo de situação. Este não é um açúcar sintático vazio; pode levar a um código mais rápido e interações mais sensíveis com corrotinas .

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)não há garantia para hasattr(item, "iteritems"). Verificar collections.Mappingé melhor.
Fred Foo

1
@larsmans, você está certo, é claro. Eu estava pensando que usar Iterabletornaria essa solução mais generalizada, esquecendo que, obviamente, iteráveis ​​não necessariamente têm iteritems.
remetente

+1 a esta resposta porque é uma solução geral que funciona para este problema, mas não se restringe a apenas imprimir os valores. @Takkun, você definitivamente deve considerar esta opção. A longo prazo, você desejará mais do que apenas imprimir os valores.
Alejandro Piad

1
@ Seanny123, Obrigado por chamar minha atenção para isso. O Python 3 muda a imagem de algumas maneiras, na verdade - vou reescrever isso como uma versão que usa a nova yield fromsintaxe.
remetente de

25

Solução 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
Sim, é assim que eu imaginei que fosse. Obrigado. Então, a vantagem disso é que não vai estourar a pilha para aninhamentos extremamente profundos? Ou há algo mais nisso?
Niklas B.

@NiklasB .: sim, esse é o primeiro benefício. Além disso, esta versão pode ser adaptada a diferentes ordens de passagem com bastante facilidade, substituindo a pilha (a list) por uma dequeou mesmo uma fila de prioridade.
Fred Foo

Sim, faz sentido. Obrigado e feliz codificação :)
Niklas B.

Sim, mas esta solução consome mais espaço do que a minha e a recursiva.
schlamar

1
@ ms4py: Por diversão, criei um benchmark . No meu computador, a versão recursiva é mais rápida e larsmans é o segundo em todos os três dicionários de teste. A versão que usa geradores é relativamente lenta, como esperado (porque tem que fazer muitos malabarismos com os diferentes contextos do gerador)
Niklas B.

9

Uma versão ligeiramente diferente que escrevi que mantém o controle das chaves ao longo do caminho para chegar lá

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

Em seus dados, será impresso

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

Também é fácil modificá-lo para rastrear o prefixo como uma tupla de chaves, em vez de uma string, se for necessário.


Como adicionar a saída a uma lista?
Shash

5

Aqui está a maneira pítônica de fazer isso. Esta função permitirá que você percorra o par de valores-chave em todos os níveis. Não salva tudo na memória, mas sim percorre o dicionário enquanto você o percorre

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)

Impressões

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

2

Solução 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

Como é isso? Big O deve ser o mesmo (é O(depth)para a solução recursiva. O mesmo se aplica a esta versão, se estou pensando corretamente).
Niklas B.

"Copiar a pilha"? Do que você está falando? Cada chamada de função cria um novo stackframe. Sua solução usa iterscomo uma pilha explícita, então o consumo de memória Big-O é o mesmo, ou estou perdendo alguma coisa?
Niklas B.

@NiklasB. A recursão sempre vem com sobrecarga, veja esta seção da Wikipedia para detalhes: en.wikipedia.org/wiki/… O frame da pilha da solução recursiva é muito maior.
schlamar

Você deve estar entendendo mal esse parágrafo. Não diz nada para apoiar suas declarações.
Niklas B.

1
@NiklasB. Não, porque o frame da pilha aqui é apenas o iter e para a solução recursiva o frame da pilha tem o iter, contador do programa, ambiente variável, etc ...
schlamar

2

Uma solução alternativa para trabalhar com listas baseada na solução 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)

2

Estou usando o código a seguir para imprimir todos os valores de um dicionário aninhado, levando em consideração onde o valor pode ser uma lista contendo dicionários. Isso foi útil para mim ao analisar um arquivo JSON em um dicionário e precisar verificar rapidamente se algum de seus valores é 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)

Resultado:

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

Tenho um problema muito semelhante aqui stackoverflow.com/questions/50642922/… . Existe uma maneira de encontrar o último elemento da lista do dicionário, excluí-lo e subir de nível? Se não excluir, quero fazer uma lista em que o último elemento é a profundidade dos dados, então
inverto

1

Aqui está uma versão modificada da resposta de Fred Foo para Python 2. Na resposta original, apenas o nível mais profundo de aninhamento é gerado. Se você produzir as chaves como listas, poderá mantê-las para todos os níveis, embora para referenciá-las seja necessário referenciar uma lista de listas.

Esta é a função:

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 fazer referência às chaves:

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

para um dicionário de três níveis.

Você precisa saber o número de níveis antes de acessar várias chaves e o número de níveis deve ser constante (pode ser possível adicionar um pequeno script para verificar o número de níveis de aninhamento ao iterar por meio de valores, mas eu não ainda olhou para isso).


1

Acho essa abordagem um pouco mais flexível, aqui você apenas fornece uma função de gerador que emite pares de chave e valor e pode ser facilmente estendida para também iterar em 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

Então você pode escrever sua própria myprintfunção e imprimir esses pares de valores-chave.

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

Um teste:

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

Resultado:

status : good
target : 1
port : 11

Eu testei isso no Python 3.6.


0

Essas respostas funcionam para apenas 2 níveis de subdicionários. Para mais experimente isto:

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)
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.