Parcourir toutes les valeurs de dictionnaire imbriquées?

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

J'essaie de parcourir un dictionnaire et d'imprimer toutes les paires de valeurs clés où la valeur n'est pas un dictionnaire imbriqué. Si la valeur est un dictionnaire, je veux y entrer et imprimer ses paires de valeurs clés ... etc. De l'aide?

ÉDITER

Que dis-tu de ça? Il n'imprime toujours qu'une seule chose.

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

Cas de test complet

Dictionnaire:

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

Résultat:

xml : {u'config': {u'portstatus': {u'status': u'good'}, u'target': u'1'}, u'port': u'11'}
Takkun
la source
1
On dirait que vous voulez une récursivité, mais la description n'est pas assez claire pour être sûre. Qu'en est-il d'un exemple d'entrée / sortie? De plus, quel est le problème avec votre code?
Niklas B.
2
Il y a une limite de récursion fixe en Python: docs.python.org/library/sys.html#sys.setrecursionlimit
-Philip Gehrcke
2
@ Jan-PhilipGehrcke: Implémenter des algorithmes sur une structure de données arborescente sans récursivité est tout simplement un suicide.
Niklas B.
2
@Takkun: Vous utilisez dictcomme nom de variable. Ne faites jamais cela (c'est pourquoi cela échoue).
Niklas B.
3
@NiklasB., Re: "suicide": Je viens d'implémenter une version itérative de l'algorithme de Scharron et ses deux lignes plus longues et toujours assez faciles à suivre. De plus, traduire la récursivité en itération est souvent une exigence pour passer des arbres aux graphes généraux.
Fred Foo

Réponses:

157

Comme le dit Niklas, vous avez besoin de récursivité, c'est-à-dire que vous voulez définir une fonction pour imprimer votre dict, et si la valeur est un dict, vous voulez appeler votre fonction d'impression en utilisant ce nouveau dict.

Quelque chose comme :

def myprint(d):
    for k, v in d.items():
        if isinstance(v, dict):
            myprint(v)
        else:
            print("{0} : {1}".format(k, v))
Scharron
la source
3
petite amélioration. ajoutez print (k), avant d'appeler myprint (v).
Naomi Fridman
Avec la récursivité, c'est trivial.
sergzach
36

Il y a des problèmes potentiels si vous écrivez votre propre implémentation récursive ou l'équivalent itératif avec stack. Voir cet exemple:

    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

Au sens normal, le dictionnaire imbriqué sera un arbre n-naire semblable à une structure de données. Mais la définition n'exclut pas la possibilité d'un bord croisé ou même d'un bord arrière (donc plus un arbre). Par exemple, ici key2.2 contient le dictionnaire à partir de key1 , key2.3 pointe vers le dictionnaire entier (bord arrière / cycle). Lorsqu'il y a un bord arrière (cycle), la pile / récursivité s'exécutera indéfiniment.

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

Si vous imprimez ce dictionnaire avec cette implémentation de Scharron

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

Vous verriez cette erreur:

    RuntimeError: maximum recursion depth exceeded while calling a Python object

Il en va de même avec l'implémentation de senderle .

De même, vous obtenez une boucle infinie avec cette implémentation 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))

Cependant, Python détecte réellement les cycles dans le dictionnaire imbriqué:

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

"{...}" est l'endroit où un cycle est détecté.

Comme demandé par Moondra, c'est un moyen d'éviter les cycles (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
la source
alors comment implémenteriez-vous une solution itérative?
dreftymac
2
@dreftymac j'ajouterais un ensemble visité pour les clés pour éviter les cycles d'aller: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
Merci de l'avoir signalé. Pourriez-vous inclure votre code dans la réponse. Je pense que cela complète votre excellente réponse.
Moondra
Pour Python3, utiliser list(d.items())comme d.items()renvoie une vue, pas une liste, et utiliser à la v.items()place dev.iteritems()
Max
33

Étant donné que a dictest itérable, vous pouvez appliquer la formule itérable classique des conteneurs imbriqués à ce problème avec seulement quelques modifications mineures. Voici une version de Python 2 (voir ci-dessous pour 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

Tester:

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

Dans Python 2, il peut être possible de créer une personnalisation Mappingqualifiée de Mappingmais ne contenant pas iteritems, auquel cas cela échouera. Les documents n'indiquent pas que iteritemsc'est nécessaire pour un Mapping; d'autre part, la source donne aux Mappingtypes une iteritemsméthode. Donc pour la coutume Mappings, héritez decollections.Mapping explicitement juste au cas où.

Dans Python 3, il y a un certain nombre d'améliorations à apporter. Depuis Python 3.3, les classes de base abstraites vivent dans collections.abc. Ils restent collectionségalement pour la compatibilité ascendante, mais il est plus agréable d'avoir nos classes de base abstraites ensemble dans un espace de noms. Donc, cela importe abcde collections. Python 3.3 ajoute également yield from, qui est conçu uniquement pour ce genre de situations. Ce n'est pas du sucre syntaxique vide; cela peut conduire à un code plus rapide et à des interactions plus sensibles avec les coroutines .

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
expéditeur
la source
3
isinstance(item, collections.Iterable)n'est pas une garantie pour hasattr(item, "iteritems"). Vérifier, collections.Mappingc'est mieux.
Fred Foo
1
@larsmans, vous avez tout à fait raison, bien sûr. Je pensais que l'utilisation Iterablerendrait cette solution plus généralisée, oubliant que, évidemment, les itérables ne l'ont pas forcément iteritems.
senderle
+1 à cette réponse parce que c'est une solution générale qui fonctionne pour ce problème, mais elle ne se limite pas à l'impression des valeurs. @Takkun, vous devriez certainement envisager cette option. À long terme, vous voudrez plus que simplement imprimer les valeurs.
Alejandro Piad
1
@ Seanny123, Merci d'avoir attiré mon attention sur cela. Python 3 change l'image de deux manières, en fait - je vais réécrire cela comme une version qui utilise la nouvelle yield fromsyntaxe.
expéditeur le
25

Solution itérative alternative:

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
la source
2
Ouais, c'est comme ça que je l'imaginais. Merci. Donc, l'avantage de ceci est qu'il ne débordera pas la pile pour des imbrications extrêmement profondes? Ou y a-t-il autre chose?
Niklas B.
@NiklasB .: oui, c'est le premier avantage. Aussi, cette version peut être adaptée aux différents ordres de parcours assez facilement en remplaçant la pile (a list) par une dequevoire une file prioritaire.
Fred Foo
Oui, ça a du sens. Merci et bon codage :)
Niklas B.
Oui, mais cette solution prend plus de place que la mienne et la récursive.
schlamar
1
@ ms4py: Pour le plaisir, j'ai créé un benchmark . Sur mon ordinateur, la version récursive est la plus rapide et larsmans est le deuxième pour les trois dictionnaires de test. La version utilisant des générateurs est relativement lente, comme prévu (car elle doit beaucoup jongler avec les différents contextes de générateur)
Niklas B.
9

Version légèrement différente que j'ai écrite qui garde la trace des clés tout au long du chemin pour y arriver

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

Sur vos données, il s'imprimera

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

Il est également facile de le modifier pour suivre le préfixe comme un tuple de clés plutôt qu'une chaîne si vous en avez besoin de cette façon.

Ehsan Kia
la source
Comment ajouter la sortie à une liste?
Shash
5

Voici une façon pythonique de le faire. Cette fonction vous permettra de parcourir une paire clé-valeur dans tous les niveaux. Il ne sauvegarde pas le tout dans la mémoire mais parcourt le dict au fur et à mesure que vous le parcourez

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)

Tirages

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
la source
3

Une solution alternative pour travailler avec des listes basées sur la solution 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
la source
2

Solution itérative comme alternative:

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
la source
Comment c'est? Big O devrait être le même (c'est O(depth)pour la solution récursive. Il en va de même pour cette version, si je pense correctement).
Niklas B.
"Copiez la pile"? Qu'est-ce que tu racontes? Chaque appel de fonction crée un nouveau stackframe. Votre solution utilise iterscomme une pile explicite, donc la consommation de mémoire Big-O est la même, ou est-ce que je manque quelque chose?
Niklas B.
@NiklasB. La récursion s'accompagne toujours d'une surcharge, voir cette section sur Wikipedia pour plus de détails: en.wikipedia.org/wiki/... Le cadre de pile de la solution récursive est beaucoup plus grand.
schlamar
Vous devez mal comprendre ce paragraphe. Cela ne dit rien pour soutenir vos déclarations.
Niklas B.
1
@NiklasB. Non, parce que le frame de pile ici n'est que l'iter et pour la solution récursive le frame de pile a l'iter, le compteur de programme, l'environnement variable, etc ...
schlamar
2

J'utilise le code suivant pour imprimer toutes les valeurs d'un dictionnaire imbriqué, en tenant compte de l'endroit où la valeur pourrait être une liste contenant des dictionnaires. Cela m'a été utile lorsque j'ai analysé un fichier JSON dans un dictionnaire et que j'ai besoin de vérifier rapidement si l'une de ses valeurs l'est 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)

Production:

    10
    2017-03-15T14:02:49.301000
    bar
    some_string
sigma
la source
J'ai un problème très similaire ici stackoverflow.com/questions/50642922/… . Existe-t-il un moyen de trouver le dernier élément de la liste du dictionnaire, de le supprimer et de remonter d'un niveau? Sinon supprimer, je veux faire une liste où le dernier élément est la profondeur des données donc
j'inverse
1

Voici une version modifiée de la réponse de Fred Foo pour Python 2. Dans la réponse originale, seul le niveau le plus profond d'imbrication est affiché. Si vous sortez les clés sous forme de listes, vous pouvez conserver les clés pour tous les niveaux, bien que pour les référencer, vous devez référencer une liste de listes.

Voici la fonction:

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

Pour référencer les clés:

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

pour un dictionnaire à trois niveaux.

Vous devez connaître le nombre de niveaux avant d'accéder à plusieurs clés et le nombre de niveaux doit être constant (il est peut-être possible d'ajouter un petit morceau de script pour vérifier le nombre de niveaux d'imbrication lors de l'itération de valeurs, mais je ne l'ai pas fait encore regardé cela).

ÉpicéBaguette
la source
1

Je trouve cette approche un peu plus flexible, ici vous fournissez simplement une fonction de générateur qui émet des paires clé, valeur et peut être facilement étendue pour itérer également sur des listes.

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

Ensuite, vous pouvez écrire votre propre myprintfonction, puis imprimer ces paires de valeurs clés.

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

Un examen:

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

Production:

status : good
target : 1
port : 11

J'ai testé cela sur Python 3.6.

sirex
la source
0

Ces réponses ne fonctionnent que pour 2 niveaux de sous-dictionnaires. Pour en savoir plus, essayez ceci:

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
la source