Alle verschachtelten Wörterbuchwerte durchlaufen?

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

Ich versuche, ein Wörterbuch zu durchlaufen und alle Schlüsselwertpaare auszudrucken, bei denen der Wert kein verschachteltes Wörterbuch ist. Wenn der Wert ein Wörterbuch ist, möchte ich ihn aufrufen und seine Schlüsselwertpaare ausdrucken ... usw. Irgendeine Hilfe?

BEARBEITEN

Wie wäre es damit? Es wird immer noch nur eine Sache gedruckt.

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

Vollständiger Testfall

Wörterbuch:

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

Ergebnis:

xml : {u'config': {u'portstatus': {u'status': u'good'}, u'target': u'1'}, u'port': u'11'}
Takkun
quelle
1
Klingt so, als ob Sie eine Rekursion wünschen, aber die Beschreibung ist nicht klar genug, um sicher zu sein. Was ist mit einem Beispiel für Ein- / Ausgabe? Was stimmt nicht mit Ihrem Code?
Niklas B.
2
Es gibt ein festes Rekursionslimit in Python: docs.python.org/library/sys.html#sys.setrecursionlimit
Dr. Jan-Philip Gehrcke
2
@ Jan-PhilipGehrcke: Algorithmen in einer baumartigen Datenstruktur ohne Rekursion zu implementieren, ist Selbstmord.
Niklas B.
2
@Takkun: Sie verwenden dictals Variablennamen. Tun Sie dies niemals (deshalb schlägt es fehl).
Niklas B.
3
@NiklasB., Re: "Selbstmord": Ich habe gerade eine iterative Version von Scharrons Algorithmus implementiert und es ist nur zwei Zeilen länger und immer noch recht einfach zu folgen. Außerdem ist die Übersetzung von Rekursion in Iteration häufig erforderlich, wenn von Bäumen zu allgemeinen Graphen gewechselt wird.
Fred Foo

Antworten:

156

Wie von Niklas gesagt, benötigen Sie eine Rekursion, dh Sie möchten eine Funktion zum Drucken Ihres Diktats definieren. Wenn der Wert ein Diktat ist, möchten Sie Ihre Druckfunktion mit diesem neuen Diktat aufrufen.

Etwas wie :

def myprint(d):
    for k, v in d.items():
        if isinstance(v, dict):
            myprint(v)
        else:
            print("{0} : {1}".format(k, v))
Scharron
quelle
3
kleine Verbesserung. Fügen Sie print (k) hinzu, bevor Sie myprint (v) aufrufen.
Naomi Fridman
Mit Rekursion ist es trivial.
Sergzach
36

Es gibt potenzielle Probleme, wenn Sie Ihre eigene rekursive Implementierung oder das iterative Äquivalent mit Stack schreiben. Siehe dieses Beispiel:

    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

Im normalen Sinne ist das verschachtelte Wörterbuch eine n-näre baumähnliche Datenstruktur. Die Definition schließt jedoch nicht die Möglichkeit einer Querkante oder sogar einer Hinterkante (also nicht länger eines Baumes) aus. Hier gilt beispielsweise key2.2 für das Wörterbuch von key1 , key2.3 zeigt auf das gesamte Wörterbuch (Hinterkante / Zyklus). Wenn es eine Hinterkante (Zyklus) gibt, wird der Stapel / die Rekursion unendlich ausgeführt.

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

Wenn Sie dieses Wörterbuch mit dieser Implementierung von Scharron drucken

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

Sie würden diesen Fehler sehen:

    RuntimeError: maximum recursion depth exceeded while calling a Python object

Gleiches gilt für die Implementierung von senderle .

Ebenso erhalten Sie mit dieser Implementierung eine Endlosschleife von 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))

Python erkennt jedoch tatsächlich Zyklen im verschachtelten Wörterbuch:

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

"{...}" ist der Ort, an dem ein Zyklus erkannt wird.

Wie von Moondra gefordert, ist dies eine Möglichkeit, Zyklen (DFS) zu vermeiden:

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
quelle
Wie würden Sie dann eine iterative Lösung implementieren?
Dreftymac
2
@dreftymac Ich würde ein besuchtes Set für Schlüssel hinzufügen, um Zyklen zu vermeiden: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
Vielen Dank für den Hinweis. Würde es Ihnen etwas ausmachen, Ihren Code in die Antwort aufzunehmen? Ich denke, es vervollständigt Ihre ausgezeichnete Antwort.
Moondra
Verwenden Sie für Python3 list(d.items())als d.items()Rückgabe eine Ansicht, keine Liste, und verwenden Sie v.items()stattdessenv.iteritems()
Max
33

Da a dictiterierbar ist, können Sie die klassische iterierbare Formel für verschachtelte Container mit nur wenigen geringfügigen Änderungen auf dieses Problem anwenden . Hier ist eine Python 2-Version (siehe unten für 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

Prüfung:

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

In Python 2 ist es möglicherweise möglich, einen benutzerdefinierten Benutzer zu erstellen Mapping, der als qualifiziert ist, Mappingaber keinen enthält iteritems. In diesem Fall schlägt dies fehl. Die Dokumente geben nicht an, dass iteritemsdies für a erforderlich ist Mapping. Andererseits gibt die Quelle den MappingTypen eine iteritemsMethode. Also für benutzerdefinierte Mappings, von collections.Mappingexplizit nur für den Fall erben .

In Python 3 müssen einige Verbesserungen vorgenommen werden. Ab Python 3.3 leben abstrakte Basisklassen in collections.abc. Aus collectionsGründen der Abwärtskompatibilität bleiben sie ebenfalls vorhanden, aber es ist schöner, wenn unsere abstrakten Basisklassen in einem Namespace zusammengefasst sind. Das importiert also abcaus collections. Python 3.3 fügt hinzu yield from, das genau für diese Art von Situationen entwickelt wurde. Dies ist kein leerer syntaktischer Zucker; Dies kann zu schnellerem Code und sinnvolleren Interaktionen mit Coroutinen führen .

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
quelle
3
isinstance(item, collections.Iterable)ist keine Garantie für hasattr(item, "iteritems"). Überprüfen collections.Mappingist besser.
Fred Foo
1
@larsmans, du hast natürlich ganz recht. Ich dachte, dass die Verwendung Iterablediese Lösung allgemeiner machen würde , wobei ich vergaß, dass Iterables offensichtlich nicht unbedingt vorhanden sind iteritems.
senderle
+1 auf diese Antwort, da dies eine allgemeine Lösung ist, die für dieses Problem funktioniert, aber nicht nur auf das Drucken der Werte beschränkt ist. @Takkun sollten Sie auf jeden Fall diese Option in Betracht ziehen. Auf lange Sicht möchten Sie mehr als nur die Werte drucken.
Alejandro Piad
1
@ Seanny123, Danke, dass du mich darauf aufmerksam gemacht hast. Python 3 ändert das Bild in der Tat auf verschiedene Weise - ich werde dies als eine Version umschreiben, die die neue yield fromSyntax verwendet.
senderle
25

Alternative iterative Lösung:

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
quelle
2
Ja, so habe ich es mir vorgestellt. Vielen Dank. Dies hat also den Vorteil, dass der Stapel bei extrem tiefen Verschachtelungen nicht überläuft. Oder gibt es noch etwas anderes?
Niklas B.
@ NiklasB.: Ja, das ist der erste Vorteil. Diese Version kann auch ganz einfach an verschiedene Durchlaufreihenfolgen angepasst werden, indem der Stapel (a list) durch eine dequeoder sogar eine Prioritätswarteschlange ersetzt wird.
Fred Foo
Ja, macht Sinn. Vielen Dank und
Niklas B.
Ja, aber diese Lösung ist platzsparender als meine und die rekursive.
Schlamar
1
@ ms4py: Zum Spaß habe ich einen Benchmark erstellt . Auf meinem Computer ist die rekursive Version am schnellsten und larsmans ist die zweite für alle drei Testwörterbücher. Die Version mit Generatoren ist erwartungsgemäß relativ langsam (da sie viel mit den verschiedenen Generatorkontexten jonglieren muss)
Niklas B.
9

Etwas andere Version, die ich geschrieben habe und die die Schlüssel auf dem Weg dorthin verfolgt

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

Auf Ihren Daten wird gedruckt

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

Es ist auch einfach, es zu ändern, um das Präfix als Tupel von Schlüsseln und nicht als Zeichenfolge zu verfolgen, wenn Sie es auf diese Weise benötigen.

Ehsan Kia
quelle
Wie füge ich die Ausgabe einer Liste hinzu?
Shash
5

Hier ist eine pythonische Methode. Mit dieser Funktion können Sie das Schlüssel-Wert-Paar in allen Ebenen durchlaufen. Es speichert nicht das Ganze in der Erinnerung, sondern geht durch das Diktat, während Sie es durchlaufen

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)

Druckt

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
quelle
2

Iterative Lösung als 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
quelle
Wie ist das? Big O sollte gleich sein (es ist O(depth)für die rekursive Lösung. Das gleiche gilt für diese Version, wenn ich richtig denke).
Niklas B.
"Kopieren Sie den Stapel"? Worüber redest du? Jeder Funktionsaufruf erstellt einen neuen Stackframe. Ihre Lösung wird itersals expliziter Stapel verwendet, sodass der Big-O-Speicherverbrauch gleich ist, oder fehlt mir etwas?
Niklas B.
@ NiklasB. Rekursion ist immer mit Overhead verbunden. Weitere Informationen finden Sie in diesem Abschnitt bei Wikipedia: en.wikipedia.org/wiki/… Der Stapelrahmen der rekursiven Lösung ist viel größer.
Schlamar
Sie müssen diesen Absatz falsch verstehen. Es sagt nichts, um Ihre Aussagen zu stützen.
Niklas B.
1
@ NiklasB. Nein, da der Stapelrahmen hier nur der Iter ist und für die rekursive Lösung der
Stapelrahmen
2

Eine alternative Lösung für die Arbeit mit Listen, die auf der Lösung von Scharron basieren

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
quelle
2

Ich verwende den folgenden Code, um alle Werte eines verschachtelten Wörterbuchs zu drucken, wobei berücksichtigt wird, wo der Wert eine Liste mit Wörterbüchern sein kann. Dies war nützlich für mich, wenn ich eine JSON-Datei in ein Wörterbuch analysierte und schnell überprüfen musste, ob einer ihrer Werte vorhanden ist 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)

Ausgabe:

    10
    2017-03-15T14:02:49.301000
    bar
    some_string
Sigma
quelle
Ich habe hier ein ähnliches Problem. Stackoverflow.com/questions/50642922/… . Gibt es eine Möglichkeit, das letzte Element der Liste des Wörterbuchs zu finden, dieses zu löschen und dann eine Ebene nach oben zu verschieben? Wenn nicht löschen, möchte ich eine Liste erstellen, in der das letzte Element die Tiefe der Daten ist, also
kehre
1

Hier ist eine modifizierte Version von Fred Foos Antwort für Python 2. In der ursprünglichen Antwort wird nur die tiefste Verschachtelungsebene ausgegeben. Wenn Sie die Schlüssel als Listen ausgeben, können Sie die Schlüssel für alle Ebenen behalten. Um sie zu referenzieren, müssen Sie jedoch auf eine Liste von Listen verweisen.

Hier ist die Funktion:

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

So verweisen Sie auf die Schlüssel:

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

für ein dreistufiges Wörterbuch.

Sie müssen die Anzahl der Ebenen kennen, bevor Sie auf mehrere Schlüssel zugreifen können, und die Anzahl der Ebenen sollte konstant sein (es kann möglich sein, ein kleines Skript hinzuzufügen, um die Anzahl der Verschachtelungsebenen beim Durchlaufen von Werten zu überprüfen, aber ich habe es nicht getan noch angeschaut).

SpicyBaguette
quelle
1

Ich finde diesen Ansatz etwas flexibler. Hier stellen Sie lediglich eine Generatorfunktion bereit, die Schlüssel- und Wertepaare ausgibt und einfach erweitert werden kann, um auch über Listen zu iterieren.

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

Dann können Sie Ihre eigene myprintFunktion schreiben und dann diese Schlüsselwertpaare drucken.

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

Ein Test:

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

Ausgabe:

status : good
target : 1
port : 11

Ich habe dies auf Python 3.6 getestet.

Sirex
quelle
0

Diese Antworten funktionieren nur für zwei Ebenen von Unterwörterbüchern. Für mehr versuchen Sie dies:

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
quelle