web-dev-qa-db-fra.com

Parcourez toutes les valeurs de dictionnaire imbriquées?

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 aller et imprimer ses paires clé-valeur ... etc. De l'aide?

MODIFIER

Que dis-tu de ça? Il n’imprime toujours qu’une 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'}
73
Takkun

Comme le dit Niklas, vous avez besoin de récursivité, c’est-à-dire que vous souhaitez définir une fonction pour imprimer votre dict, et si la valeur est un dict, vous souhaitez appeler votre fonction print à l’aide de ce nouveau dict.

Quelque chose comme :

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

Ou à partir de Python 3:

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

Etant donné que dict est itérable, vous pouvez appliquer la formule classique formule itérable par conteneur imbriqué à ce problème avec seulement quelques modifications mineures. Voici une version 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 est possible de créer une Mapping personnalisée pouvant être qualifiée de Mapping mais ne contient pas iteritems, auquel cas cela échouera. La documentation n'indique pas que iteritems est requis pour un Mapping; d'autre part, le source donne Mapping tape une méthode iteritems. Donc, pour la coutume Mappings, héritez de collections.Mapping explicitement au cas où.

Dans Python 3, un certain nombre d'améliorations doivent être apportées. À partir de Python3.3, les classes de base abstraites résident dans collections.abc. Ils restent dans collections également pour des raisons de compatibilité ascendante, mais il est plus agréable de regrouper nos classes de base abstraites dans un seul espace de noms. Donc cela importe abc de collections. Python 3.3 ajoute également yield from, conçu pour ce type de situations. Ce n'est pas un sucre syntaxique vide; cela peut conduire à code plus rapide et à des interactions plus sensibles avec 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
27
senderle

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))
20
Fred Foo

Il y a des problèmes potentiels si vous écrivez votre propre implémentation récursive ou l'équivalent itératif avec pile. 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

Dans le sens normal, dictionnaire imbriqué sera un arbre n-nary comme structure de données. Mais la définition n'exclut pas la possibilité d'un bord en travers ou même d'un bord en arrière (donc plus un arbre). Par exemple, ici key2.2 correspond au dictionnaire car key1, key2.3 pointe vers le dictionnaire entier (bord arrière/cycle). Lorsqu'il y a un bord arrière (cycle), la pile/la récursivité s'exécutent 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 une façon 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)
17
tengr

Version légèrement différente que j'ai écrite et qui garde une trace des clés le 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, ça va imprimer

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 sous forme de tuple de clés plutôt que de chaîne si vous en avez besoin.

6
Ehsan Kia

Voici comment pythonique le faire. Cette fonction vous permettra de parcourir la paire clé-valeur dans tous les niveaux. Il n'enregistre pas le tout dans la mémoire mais passe en revue le dict lorsque 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)

Impressions

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

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
1
schlamar

Voici une version modifiée de la réponse de Fred Foo pour Python 2. Dans la réponse d'origine, seul le niveau d'imbrication le plus profond est généré. Si vous produisez les clés sous forme de listes, vous pouvez les conserver pour tous les niveaux. Vous devez toutefois référencer une liste de listes pour les référencer. 

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 script pour vérifier le nombre de niveaux d'imbrication lors de l'itération de valeurs, mais je ne l'ai pas déjà. encore regardé cela). 

1
SpicyBaguette

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

J'utilise le code suivant pour imprimer toutes les valeurs d'un dictionnaire imbriqué, en tenant compte du fait que la valeur peut être une liste contenant des dictionnaires. Cela m'a été utile lors de l'analyse d'un fichier JSON dans un dictionnaire et de la nécessité de vérifier rapidement si l'une de ses valeurs 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)

Sortie:

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

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 parcourir également 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 fonction myprint, puis imprimer ces paires de valeurs de clé.

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',
    },
})

Sortie:

status : good
target : 1
port : 11

J'ai testé cela sur Python 3.6.

0
sirex